From fca6f352e4a73b26b17244d66d53145cf7668281 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Thu, 10 May 2012 15:54:58 -0700 Subject: [PATCH 01/42] Add firefox ui fallback on error/unsupported feature. --- .../firefox/components/PdfStreamConverter.js | 39 ++++++++++++++++++- l10n/en-US/viewer.properties | 3 ++ web/viewer.js | 5 +++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/extensions/firefox/components/PdfStreamConverter.js b/extensions/firefox/components/PdfStreamConverter.js index 49fd134ae..57647d67e 100644 --- a/extensions/firefox/components/PdfStreamConverter.js +++ b/extensions/firefox/components/PdfStreamConverter.js @@ -11,12 +11,14 @@ const Cr = Components.results; const Cu = Components.utils; const PDFJS_EVENT_ID = 'pdf.js.message'; const PDF_CONTENT_TYPE = 'application/pdf'; -const EXT_PREFIX = 'extensions.uriloader@pdf.js'; +const EXT_ID = 'uriloader@pdf.js'; +const EXT_PREFIX = 'extensions.' + EXT_ID; const MAX_DATABASE_LENGTH = 4096; Cu.import('resource://gre/modules/XPCOMUtils.jsm'); Cu.import('resource://gre/modules/Services.jsm'); Cu.import('resource://gre/modules/NetUtil.jsm'); +Cu.import('resource://gre/modules/AddonManager.jsm'); let privateBrowsing = Cc['@mozilla.org/privatebrowsing;1'] .getService(Ci.nsIPrivateBrowsingService); @@ -59,6 +61,13 @@ function getDOMWindow(aChannel) { return win; } +// Fake l10n until we get the real l10n sorted out. +var mozL10n = { + get: function(key, args, fallback) { + return fallback; + } +}; + // All the priviledged actions. function ChromeActions() { this.inPrivateBrowswing = privateBrowsing.privateBrowsingEnabled; @@ -115,10 +124,36 @@ ChromeActions.prototype = { }, pdfBugEnabled: function() { return getBoolPref(EXT_PREFIX + '.pdfBugEnabled', false); + }, + fallback: function(url) { + var self = this; + var message = mozL10n.get('unsupported_feature', null, + 'An unsupported feature was detected in this PDF document.'); + var win = Services.wm.getMostRecentWindow('navigator:browser'); + var notificationBox = win.gBrowser.getNotificationBox(); + var buttons = [{ + label: mozL10n.get('download_document', null, 'Download Document'), + accessKey: null, + callback: function() { + self.download(url); + } + }, { + label: mozL10n.get('disable_pdfjs', null, + 'Disable Mozilla PDF Viewer'), + accessKey: null, + callback: function() { + AddonManager.getAddonByID(EXT_ID, function(aAddon) { + aAddon.userDisabled = true; + win.gBrowser.contentWindow.location.reload(); + }); + } + }]; + notificationBox.appendNotification(message, 'pdfjs-fallback', null, + notificationBox.PRIORITY_WARNING_LOW, + buttons); } }; - // Event listener to trigger chrome privedged code. function RequestListener(actions) { this.actions = actions; diff --git a/l10n/en-US/viewer.properties b/l10n/en-US/viewer.properties index c8dbe4aba..c1617b0bd 100644 --- a/l10n/en-US/viewer.properties +++ b/l10n/en-US/viewer.properties @@ -42,3 +42,6 @@ zoom_in_label=Zoom In zoom.title=Zoom thumb_page_title=Page {{page}} thumb_page_canvas=Thumbnail of Page {{page}} +unsupported_feature=An unsupported feature was detected in this PDF document. +download_document=Download Document +disable_pdfjs=Disable Mozilla PDF Viewer diff --git a/web/viewer.js b/web/viewer.js index dcbfcf14e..d700e11f3 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -374,6 +374,11 @@ var PDFView = { } }, + fallback: function pdfViewDownload() { + var url = this.url.split('#')[0]; + FirefoxCom.request('fallback', url); + }, + navigateTo: function pdfViewNavigateTo(dest) { if (typeof dest === 'string') dest = this.destinations[dest]; From 0a30d3961be76711ef03b468100ac9a22c156705 Mon Sep 17 00:00:00 2001 From: Jakob Miland Date: Mon, 14 May 2012 20:45:07 +0200 Subject: [PATCH 02/42] Support password and add the relevant l10n strings --- l10n/da/viewer.properties | 1 + l10n/en-US/viewer.properties | 1 + src/api.js | 63 ++++++++++++++++++++++++++++-------- src/core.js | 15 +++++---- src/crypto.js | 6 ++-- src/obj.js | 5 ++- src/util.js | 14 ++++++-- src/worker.js | 25 ++++++++++++-- web/viewer.js | 31 ++++++++++++------ 9 files changed, 122 insertions(+), 39 deletions(-) diff --git a/l10n/da/viewer.properties b/l10n/da/viewer.properties index 6d208db70..e098a7939 100644 --- a/l10n/da/viewer.properties +++ b/l10n/da/viewer.properties @@ -29,3 +29,4 @@ page_of=af {{pageCount}} no_outline=Ingen dokumentoversigt tilgængelig open_file.title=Åbn fil text_annotation_type=[{{type}} Kommentar] +request_password=PDF filen er beskyttet med et kodeord: diff --git a/l10n/en-US/viewer.properties b/l10n/en-US/viewer.properties index c8dbe4aba..d0ba6ffac 100644 --- a/l10n/en-US/viewer.properties +++ b/l10n/en-US/viewer.properties @@ -42,3 +42,4 @@ zoom_in_label=Zoom In zoom.title=Zoom thumb_page_title=Page {{page}} thumb_page_canvas=Thumbnail of Page {{page}} +request_password=PDF is protected by a password: diff --git a/src/api.js b/src/api.js index 1efb22caa..cd10ee366 100644 --- a/src/api.js +++ b/src/api.js @@ -7,20 +7,46 @@ * is used, which means it must follow the same origin rules that any XHR does * e.g. No cross domain requests without CORS. * - * @param {string|TypedAray} source Either a url to a PDF is located or a - * typed array (Uint8Array) already populated with data. - * @param {Object} headers An object containing the http headers like this: - * { Authorization: "BASIC XXX" }. + * @param {string|TypedAray|object} source Can be an url to where a PDF is + * located, a typed array (Uint8Array) already populated with data or + * and parameter object with the following possible fields: + * - url - The URL of the PDF. + * - data - A typed array with PDF data. + * - httpHeaders - Basic authentication headers. + * - password - For decrypting password-protected PDFs. + * * @return {Promise} A promise that is resolved with {PDFDocumentProxy} object. */ -PDFJS.getDocument = function getDocument(source, headers) { +PDFJS.getDocument = function getDocument(source) { + var url, data, headers, password, parameters = {}; + if (typeof source === 'string') { + url = params; + } else if (isArrayBuffer(source)) { + data = source; + } else if (typeof source === 'object') { + url = source.url; + data = source.data; + headers = source.httpHeaders; + password = source.password; + parameters.password = password || null; + + if (!url && !data) + error('Invalid parameter array, need either .data or .url'); + } else { + error('Invalid parameter in getDocument, need either Uint8Array, ' + + 'string or a parameter object'); + } + var promise = new PDFJS.Promise(); var transport = new WorkerTransport(promise); - if (typeof source === 'string') { + if (data) { + // assuming the data is array, instantiating directly from it + transport.sendData(data, parameters); + } else if (url) { // fetch url PDFJS.getPdf( { - url: source, + url: url, progress: function getPDFProgress(evt) { if (evt.lengthComputable) promise.progress({ @@ -35,12 +61,10 @@ PDFJS.getDocument = function getDocument(source, headers) { headers: headers }, function getPDFLoad(data) { - transport.sendData(data); + transport.sendData(data, parameters); }); - } else { - // assuming the source is array, instantiating directly from it - transport.sendData(source); } + return promise; }; @@ -122,6 +146,11 @@ var PDFDocumentProxy = (function PDFDocumentProxyClosure() { }); return promise; }, + isEncrypted: function PDFDocumentProxy_isEncrypted() { + var promise = new PDFJS.Promise(); + promise.resolve(this.pdfInfo.encrypted); + return promise; + }, destroy: function PDFDocumentProxy_destroy() { this.transport.destroy(); } @@ -467,6 +496,14 @@ var WorkerTransport = (function WorkerTransportClosure() { this.workerReadyPromise.resolve(pdfDocument); }, this); + messageHandler.on('NeedPassword', function transportPassword(data) { + this.workerReadyPromise.reject(data.exception.message, data.exception); + }, this); + + messageHandler.on('IncorrectPassword', function transportBadPass(data) { + this.workerReadyPromise.reject(data.exception.message, data.exception); + }, this); + messageHandler.on('GetPage', function transportPage(data) { var pageInfo = data.pageInfo; var page = new PDFPageProxy(pageInfo, this); @@ -569,8 +606,8 @@ var WorkerTransport = (function WorkerTransportClosure() { }); }, - sendData: function WorkerTransport_sendData(data) { - this.messageHandler.send('GetDocRequest', data); + sendData: function WorkerTransport_sendData(data, params) { + this.messageHandler.send('GetDocRequest', {data: data, params: params}); }, getPage: function WorkerTransport_getPage(pageNumber, promise) { diff --git a/src/core.js b/src/core.js index 99a8dd161..bd8161691 100644 --- a/src/core.js +++ b/src/core.js @@ -320,19 +320,19 @@ var Page = (function PageClosure() { * `PDFDocument` objects on the main thread created. */ var PDFDocument = (function PDFDocumentClosure() { - function PDFDocument(arg, callback) { + function PDFDocument(arg, password) { if (isStream(arg)) - init.call(this, arg); + init.call(this, arg, password); else if (isArrayBuffer(arg)) - init.call(this, new Stream(arg)); + init.call(this, new Stream(arg), password); else error('PDFDocument: Unknown argument type'); } - function init(stream) { + function init(stream, password) { assertWellFormed(stream.length > 0, 'stream must have data'); this.stream = stream; - this.setup(); + this.setup(password); this.acroForm = this.catalog.catDict.get('AcroForm'); } @@ -423,11 +423,12 @@ var PDFDocument = (function PDFDocumentClosure() { } // May not be a PDF file, continue anyway. }, - setup: function PDFDocument_setup(ownerPassword, userPassword) { + setup: function PDFDocument_setup(password) { this.checkHeader(); var xref = new XRef(this.stream, this.startXRef, - this.mainXRefEntriesOffset); + this.mainXRefEntriesOffset, + password); this.xref = xref; this.catalog = new Catalog(xref); }, diff --git a/src/crypto.js b/src/crypto.js index dcd820554..c86551f36 100644 --- a/src/crypto.js +++ b/src/crypto.js @@ -556,7 +556,9 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() { var encryptionKey = prepareKeyData(fileIdBytes, passwordBytes, ownerPassword, userPassword, flags, revision, keyLength, encryptMetadata); - if (!encryptionKey && password) { + if (!encryptionKey && !password) { + throw new PasswordException('No password given', 'needpassword'); + } else if (!encryptionKey && password) { // Attempting use the password as an owner password var decodedPassword = decodeUserPassword(passwordBytes, ownerPassword, revision, keyLength); @@ -566,7 +568,7 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() { } if (!encryptionKey) - error('incorrect password or encryption data'); + throw new PasswordException('Incorrect Password', 'incorrectpassword'); this.encryptionKey = encryptionKey; diff --git a/src/obj.js b/src/obj.js index 9b99eb8f7..3432ac68d 100644 --- a/src/obj.js +++ b/src/obj.js @@ -298,7 +298,7 @@ var Catalog = (function CatalogClosure() { })(); var XRef = (function XRefClosure() { - function XRef(stream, startXRef, mainXRefEntriesOffset) { + function XRef(stream, startXRef, mainXRefEntriesOffset, password) { this.stream = stream; this.entries = []; this.xrefstms = {}; @@ -311,8 +311,7 @@ var XRef = (function XRefClosure() { var encrypt = trailerDict.get('Encrypt'); if (encrypt) { var fileId = trailerDict.get('ID'); - this.encrypt = new CipherTransformFactory(encrypt, - fileId[0] /*, password */); + this.encrypt = new CipherTransformFactory(encrypt, fileId[0], password); } // get the root dictionary (catalog) object diff --git a/src/util.js b/src/util.js index 140b18cf1..ef40f524b 100644 --- a/src/util.js +++ b/src/util.js @@ -58,6 +58,14 @@ function shadow(obj, prop, value) { return value; } +function PasswordException(msg, code) { + this.name = 'PasswordException'; + this.message = msg; + this.code = code; +} +PasswordException.prototype = new Error(); +PasswordException.constructor = PasswordException; + function bytesToString(bytes) { var str = ''; var length = bytes.length; @@ -456,7 +464,7 @@ var Promise = PDFJS.Promise = (function PromiseClosure() { } this.isResolved = true; - this.data = data || null; + this.data = (typeof data !== 'undefined') ? data : null; var callbacks = this.callbacks; for (var i = 0, ii = callbacks.length; i < ii; i++) { @@ -471,7 +479,7 @@ var Promise = PDFJS.Promise = (function PromiseClosure() { } }, - reject: function Promise_reject(reason) { + reject: function Promise_reject(reason, exception) { if (this.isRejected) { error('A Promise can be rejected only once ' + this.name); } @@ -484,7 +492,7 @@ var Promise = PDFJS.Promise = (function PromiseClosure() { var errbacks = this.errbacks; for (var i = 0, ii = errbacks.length; i < ii; i++) { - errbacks[i].call(null, reason); + errbacks[i].call(null, reason, exception); } }, diff --git a/src/worker.js b/src/worker.js index 25f3f52cd..84a8298a7 100644 --- a/src/worker.js +++ b/src/worker.js @@ -88,14 +88,35 @@ var WorkerMessageHandler = { handler.on('GetDocRequest', function wphSetupDoc(data) { // Create only the model of the PDFDoc, which is enough for // processing the content of the pdf. - pdfModel = new PDFDocument(new Stream(data)); + var pdfData = data.data; + var pdfPassword = data.params.password; + try { + pdfModel = new PDFDocument(new Stream(pdfData), pdfPassword); + } catch (e) { + if (e instanceof PasswordException) { + if (e.code === 'needpassword') { + handler.send('NeedPassword', { + exception: e + }); + } else if (e.code === 'incorrectpassword') { + handler.send('IncorrectPassword', { + exception: e + }); + } + + return; + } else { + throw e; + } + } var doc = { numPages: pdfModel.numPages, fingerprint: pdfModel.getFingerprint(), destinations: pdfModel.catalog.destinations, outline: pdfModel.catalog.documentOutline, info: pdfModel.getDocumentInfo(), - metadata: pdfModel.catalog.metadata + metadata: pdfModel.catalog.metadata, + encrypted: !!pdfModel.xref.encrypt }; handler.send('GetDoc', {pdfInfo: doc}); }); diff --git a/web/viewer.js b/web/viewer.js index dcbfcf14e..d4897b956 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -331,10 +331,15 @@ var PDFView = { return currentPageNumber; }, - open: function pdfViewOpen(url, scale) { - this.url = url; - - document.title = decodeURIComponent(getFileName(url)) || url; + open: function pdfViewOpen(url, scale, password) { + var parameters = {password: password}; + if (typeof url === 'string') { + this.url = url; + document.title = decodeURIComponent(getFileName(url)) || url; + parameters.url = url; + } else if (isArrayBuffer(url)) { + parameters.data = url; + } if (!PDFView.loadingBar) { PDFView.loadingBar = new ProgressBar('#loadingBar', {}); @@ -342,12 +347,23 @@ var PDFView = { var self = this; self.loading = true; - PDFJS.getDocument(url).then( + PDFJS.getDocument(parameters).then( function getDocumentCallback(pdfDocument) { self.load(pdfDocument, scale); self.loading = false; }, function getDocumentError(message, exception) { + if (exception.name === 'PasswordException') { + if (exception.code === 'needpassword') { + var promptString = mozL10n.get('request_password', null, + 'PDF is protected by a password:'); + password = prompt(promptString); + if (password && password.length > 0) { + return PDFView.open(url, scale, password); + } + } + } + var loadingIndicator = document.getElementById('loading'); loadingIndicator.textContent = mozL10n.get('loading_error_indicator', null, 'Error'); @@ -1530,10 +1546,7 @@ window.addEventListener('change', function webViewerChange(evt) { for (var i = 0; i < data.length; i++) uint8Array[i] = data.charCodeAt(i); - // TODO using blob instead? - PDFJS.getDocument(uint8Array).then(function(pdfDocument) { - PDFView.load(pdfDocument); - }); + PDFView.open(uint8Array, 0); }; // Read as a binary string since "readAsArrayBuffer" is not yet From 45d6daa8535c12a21a42734435351583f17bfea5 Mon Sep 17 00:00:00 2001 From: Jakob Miland Date: Mon, 14 May 2012 20:58:56 +0200 Subject: [PATCH 03/42] Wrong variable name, fix --- src/api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api.js b/src/api.js index cd10ee366..3ac4da565 100644 --- a/src/api.js +++ b/src/api.js @@ -20,7 +20,7 @@ PDFJS.getDocument = function getDocument(source) { var url, data, headers, password, parameters = {}; if (typeof source === 'string') { - url = params; + url = source; } else if (isArrayBuffer(source)) { data = source; } else if (typeof source === 'object') { From 034583e1a151bc35aa1680e3cf5833c20c303cf3 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Mon, 14 May 2012 17:19:09 -0700 Subject: [PATCH 04/42] Add new severity log info(). Change severity of some log messages. Trigger fallback on errors and warnings for extension. --- .../firefox/components/PdfStreamConverter.js | 2 +- src/api.js | 2 +- src/canvas.js | 9 ++- src/colorspace.js | 4 +- src/core.js | 2 +- src/evaluator.js | 2 +- src/fonts.js | 2 +- src/stream.js | 16 ++--- src/util.js | 64 ++++++++++++----- src/worker.js | 18 ++++- web/viewer.js | 69 ++++++++++++------- 11 files changed, 128 insertions(+), 62 deletions(-) diff --git a/extensions/firefox/components/PdfStreamConverter.js b/extensions/firefox/components/PdfStreamConverter.js index 57647d67e..9f5cc6bee 100644 --- a/extensions/firefox/components/PdfStreamConverter.js +++ b/extensions/firefox/components/PdfStreamConverter.js @@ -138,7 +138,7 @@ ChromeActions.prototype = { self.download(url); } }, { - label: mozL10n.get('disable_pdfjs', null, + label: mozL10n.get('disable_viewer', null, 'Disable Mozilla PDF Viewer'), accessKey: null, callback: function() { diff --git a/src/api.js b/src/api.js index bbab680ce..0e76a17ae 100644 --- a/src/api.js +++ b/src/api.js @@ -420,7 +420,7 @@ var WorkerTransport = (function WorkerTransportClosure() { messageHandler.send('test', testObj); return; } catch (e) { - warn('The worker has been disabled.'); + info('The worker has been disabled.'); } } // Either workers are disabled, not supported or have thrown an exception. diff --git a/src/canvas.js b/src/canvas.js index 9d470fbec..85e82aaa5 100644 --- a/src/canvas.js +++ b/src/canvas.js @@ -343,10 +343,13 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { this.ctx.webkitLineDashOffset = dashPhase; }, setRenderingIntent: function CanvasGraphics_setRenderingIntent(intent) { - TODO('set rendering intent: ' + intent); + // Maybe if we one day fully support color spaces this will be important + // for now we can ignore. + // TODO set rendering intent? }, setFlatness: function CanvasGraphics_setFlatness(flatness) { - TODO('set flatness: ' + flatness); + // There's no way to control this with canvas, but we can safely ignore. + // TODO set flatness? }, setGState: function CanvasGraphics_setGState(states) { for (var i = 0, ii = states.length; i < ii; i++) { @@ -841,7 +844,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { text.length += shownText.length; } } else { - malformed('TJ array element ' + e + ' is not string or num'); + error('TJ array element ' + e + ' is not string or num'); } } diff --git a/src/colorspace.js b/src/colorspace.js index 8d8290109..26e7c1f0f 100644 --- a/src/colorspace.js +++ b/src/colorspace.js @@ -451,12 +451,12 @@ var LabCS = (function LabCSClosure() { error('Invalid WhitePoint components, no fallback available'); if (this.XB < 0 || this.YB < 0 || this.ZB < 0) { - warn('Invalid BlackPoint, falling back to default'); + info('Invalid BlackPoint, falling back to default'); this.XB = this.YB = this.ZB = 0; } if (this.amin > this.amax || this.bmin > this.bmax) { - warn('Invalid Range, falling back to defaults'); + info('Invalid Range, falling back to defaults'); this.amin = -100; this.amax = 100; this.bmin = -100; diff --git a/src/core.js b/src/core.js index 6a5ec275c..13e14c702 100644 --- a/src/core.js +++ b/src/core.js @@ -7,7 +7,7 @@ var globalScope = (typeof window === 'undefined') ? this : window; var isWorker = (typeof window == 'undefined'); -var ERRORS = 0, WARNINGS = 1, TODOS = 5; +var ERRORS = 0, WARNINGS = 1, INFOS = 5; var verbosity = WARNINGS; // The global PDFJS object exposes the API diff --git a/src/evaluator.js b/src/evaluator.js index 23c9d1f65..d2ca8cfdb 100644 --- a/src/evaluator.js +++ b/src/evaluator.js @@ -453,7 +453,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { TODO('graphic state operator ' + key); break; default: - warn('Unknown graphic state operator ' + key); + info('Unknown graphic state operator ' + key); break; } } diff --git a/src/fonts.js b/src/fonts.js index 22037e724..b64ecefac 100644 --- a/src/fonts.js +++ b/src/fonts.js @@ -3633,7 +3633,7 @@ var CFFParser = (function CFFParserClosure() { ++offset; if (offset != 0) { - warn('cff data is shifted'); + info('cff data is shifted'); bytes = bytes.subarray(offset); this.bytes = bytes; } diff --git a/src/stream.js b/src/stream.js index 48c462fb2..b0e1beb34 100644 --- a/src/stream.js +++ b/src/stream.js @@ -1687,7 +1687,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { if (a1 > codingLine[codingPos]) { if (a1 > this.columns) { - warn('row is wrong length'); + info('row is wrong length'); this.err = true; a1 = this.columns; } @@ -1707,7 +1707,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { if (a1 > codingLine[codingPos]) { if (a1 > this.columns) { - warn('row is wrong length'); + info('row is wrong length'); this.err = true; a1 = this.columns; } @@ -1717,7 +1717,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { codingLine[codingPos] = a1; } else if (a1 < codingLine[codingPos]) { if (a1 < 0) { - warn('invalid code'); + info('invalid code'); this.err = true; a1 = 0; } @@ -1879,7 +1879,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { this.eof = true; break; default: - warn('bad 2d code'); + info('bad 2d code'); this.addPixels(columns, 0); this.err = true; } @@ -1942,7 +1942,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { for (var i = 0; i < 4; ++i) { code1 = this.lookBits(12); if (code1 != 1) - warn('bad rtc code: ' + code1); + info('bad rtc code: ' + code1); this.eatBits(12); if (this.encoding > 0) { this.lookBits(1); @@ -2065,7 +2065,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { if (result[0] && result[2]) return result[1]; } - warn('Bad two dim code'); + info('Bad two dim code'); return EOF; }; @@ -2098,7 +2098,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { if (result[0]) return result[1]; } - warn('bad white code'); + info('bad white code'); this.eatBits(1); return 1; }; @@ -2135,7 +2135,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { if (result[0]) return result[1]; } - warn('bad black code'); + info('bad black code'); this.eatBits(1); return 1; }; diff --git a/src/util.js b/src/util.js index 140b18cf1..bf5b11391 100644 --- a/src/util.js +++ b/src/util.js @@ -3,6 +3,8 @@ 'use strict'; +// Use only for debugging purposes. This should not be used in any code that is +// in mozilla master. function log(msg) { if (console && console.log) console.log(msg); @@ -10,9 +12,36 @@ function log(msg) { print(msg); } +// A notice for devs that will not trigger the fallback UI. These are good +// for things that are helpful to devs, such as warning that Workers were +// disabled, which is important to devs but not end users. +function info(msg) { + if (verbosity >= INFOS) { + log('Info: ' + msg); + PDFJS.LogManager.notify('info', msg); + } +} + +// Non-fatal warnings that should trigger the fallback UI. function warn(msg) { - if (verbosity >= WARNINGS) + if (verbosity >= WARNINGS) { log('Warning: ' + msg); + PDFJS.LogManager.notify('warn', msg); + } +} + +// Fatal errors that should trigger the fallback UI and halt execution by +// throwing an exception. +function error(msg) { + log('Error: ' + msg); + log(backtrace()); + PDFJS.LogManager.notify('error', msg); + throw new Error(msg); +} + +// Missing features that should trigger the fallback UI. +function TODO(what) { + warn('TODO: ' + what); } function backtrace() { @@ -23,21 +52,6 @@ function backtrace() { } } -function error(msg) { - log('Error: ' + msg); - log(backtrace()); - throw new Error(msg); -} - -function TODO(what) { - if (verbosity >= TODOS) - log('TODO: ' + what); -} - -function malformed(msg) { - error('Malformed PDF: ' + msg); -} - function assert(cond, msg) { if (!cond) error(msg); @@ -47,9 +61,25 @@ function assert(cond, msg) { // behavior is undefined. function assertWellFormed(cond, msg) { if (!cond) - malformed(msg); + error(msg); } +var LogManager = PDFJS.LogManager = (function LogManagerClosure() { + var loggers = []; + return { + addLogger: function logManager_addLogger(logger) { + loggers.push(logger); + }, + notify: function(type, message) { + for (var i = 0, ii = loggers.length; i < ii; i++) { + var logger = loggers[i]; + if (logger[type]) + logger[type](message); + } + } + }; +})(); + function shadow(obj, prop, value) { Object.defineProperty(obj, prop, { value: value, enumerable: true, diff --git a/src/worker.js b/src/worker.js index 25f3f52cd..780eff8f3 100644 --- a/src/worker.js +++ b/src/worker.js @@ -11,10 +11,13 @@ function MessageHandler(name, comObj) { var ah = this.actionHandler = {}; ah['console_log'] = [function ahConsoleLog(data) { - console.log.apply(console, data); + console.log.apply(console, data); }]; ah['console_error'] = [function ahConsoleError(data) { - console.error.apply(console, data); + console.error.apply(console, data); + }]; + ah['_warn'] = [function ah_Warn(data) { + warn(data); }]; comObj.onmessage = function messageHandlerComObjOnMessage(event) { @@ -223,6 +226,17 @@ var workerConsole = { if (typeof window === 'undefined') { globalScope.console = workerConsole; + // Add a logger so we can pass warnings on to the main thread, errors will + // throw an exception which will be forwarded on automatically. + PDFJS.LogManager.addLogger({ + warn: function(msg) { + postMessage({ + action: '_warn', + data: msg + }); + } + }); + var handler = new MessageHandler('worker_processor', this); WorkerMessageHandler.setup(handler); } diff --git a/web/viewer.js b/web/viewer.js index d700e11f3..34118e2aa 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -221,6 +221,7 @@ var PDFView = { initialBookmark: document.location.hash.substring(1), container: null, initialized: false, + fellback: false, // called once when the document is loaded initialize: function pdfViewInitialize() { this.container = document.getElementById('viewerContainer'); @@ -375,6 +376,11 @@ var PDFView = { }, fallback: function pdfViewDownload() { + // Only trigger the fallback once so we don't spam the user with messages + // for one PDF. + if (this.fellback) + return; + this.fellback = true; var url = this.url.split('#')[0]; FirefoxCom.request('fallback', url); }, @@ -441,6 +447,34 @@ var PDFView = { * and optionally a 'stack' property. */ error: function pdfViewError(message, moreInfo) { + var moreInfoText = mozL10n.get('error_build', {build: PDFJS.build}, + 'PDF.JS Build: {{build}}') + '\n'; + if (moreInfo) { + moreInfoText += + mozL10n.get('error_message', {message: moreInfo.message}, + 'Message: {{message}}'); + if (moreInfo.stack) { + moreInfoText += '\n' + + mozL10n.get('error_stack', {stack: moreInfo.stack}, + 'Stack: {{stack}}'); + } else { + if (moreInfo.filename) { + moreInfoText += '\n' + + mozL10n.get('error_file', {file: moreInfo.filename}, + 'File: {{file}}'); + } + if (moreInfo.lineNumber) { + moreInfoText += '\n' + + mozL10n.get('error_line', {line: moreInfo.lineNumber}, + 'Line: {{line}}'); + } + } + } + if (PDFJS.isFirefoxExtension) { + console.error(message + '\n' + moreInfoText); + this.fallback(); + return; + } var errorWrapper = document.getElementById('errorWrapper'); errorWrapper.removeAttribute('hidden'); @@ -467,32 +501,9 @@ var PDFView = { }; moreInfoButton.removeAttribute('hidden'); lessInfoButton.setAttribute('hidden', 'true'); - errorMoreInfo.value = - mozL10n.get('error_build', {build: PDFJS.build}, - 'PDF.JS Build: {{build}}') + '\n'; + errorMoreInfo.value = moreInfoText; - if (moreInfo) { - errorMoreInfo.value += - mozL10n.get('error_message', {message: moreInfo.message}, - 'Message: {{message}}'); - if (moreInfo.stack) { - errorMoreInfo.value += '\n' + - mozL10n.get('error_stack', {stack: moreInfo.stack}, - 'Stack: {{stack}}'); - } else { - if (moreInfo.filename) { - errorMoreInfo.value += '\n' + - mozL10n.get('error_file', {file: moreInfo.filename}, - 'File: {{file}}'); - } - if (moreInfo.lineNumber) { - errorMoreInfo.value += '\n' + - mozL10n.get('error_line', {line: moreInfo.lineNumber}, - 'Line: {{line}}'); - } - } - } - errorMoreInfo.rows = errorMoreInfo.value.split('\n').length - 1; + errorMoreInfo.rows = moreInfoText.split('\n').length - 1; }, progress: function pdfViewProgress(level) { @@ -1385,6 +1396,14 @@ window.addEventListener('load', function webViewerLoad(evt) { PDFBug.init(); } + // Listen for warnings to trigger the fallback UI. Errors should be caught + // and call PDFView.error() so we don't need to listen for those. + PDFJS.LogManager.addLogger({ + warn: function() { + PDFView.fallback(); + } + }); + var thumbsView = document.getElementById('thumbnailView'); thumbsView.addEventListener('scroll', updateThumbViewArea, true); From 03032d21c40a50ff75fd7c4cf3e046c447d44a90 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Tue, 15 May 2012 11:38:38 -0700 Subject: [PATCH 05/42] Add chrome.properties for moz central build. --- make.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/make.js b/make.js index e166b6b96..955c94b07 100755 --- a/make.js +++ b/make.js @@ -395,7 +395,8 @@ target.mozcentral = function() { 'components', '../../LICENSE'], DEFAULT_LOCALE_FILES = - [LOCALE_SRC_DIR + 'en-US/viewer.properties'], + [LOCALE_SRC_DIR + 'en-US/viewer.properties', + LOCALE_SRC_DIR + 'en-US/chrome.properties'], FIREFOX_MC_EXTENSION_FILES = ['bootstrap.js', 'icon.png', From 326b739bd18c80d3959d817c5547c1fee1ee9557 Mon Sep 17 00:00:00 2001 From: Yury Delendik Date: Tue, 15 May 2012 14:47:33 -0500 Subject: [PATCH 06/42] Adds styles for small screens --- web/viewer.css | 44 ++++++++++++++++++++++++++++++++++++++++++++ web/viewer.html | 4 ++-- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/web/viewer.css b/web/viewer.css index 7b0ce0344..00f8c0ea7 100644 --- a/web/viewer.css +++ b/web/viewer.css @@ -927,3 +927,47 @@ canvas { #PDFBug table { font-size: 10px; } + +@media all and (max-width: 770px) { + #scaleSelectContainer, #fileInput, #pageNumberLabel { + display: none; + } + + #sidebarContainer { + top: 33px; + z-index: 100; + } + #sidebarContent { + top: 33px; + background-color: hsla(0,0%,0%,.7); + } + #thumbnailView, #outlineView { + top: 66px; + } + + html[dir='ltr'] #outerContainer.sidebarOpen > #mainContainer { + left: 0px; + } + html[dir='rtl'] #outerContainer.sidebarOpen > #mainContainer { + right: 0px; + } + + #pageNumber { + width: 30px; + } +} + +@media all and (max-width: 500px) { + #viewBookmark { + display: none; + } + + html[dir='ltr'] .outerCenter { + float: left; + left: 180px; + } + html[dir='rtl'] .outerCenter { + float: right; + right: 180px; + } +} diff --git a/web/viewer.html b/web/viewer.html index 0ec05e031..45fb9f040 100644 --- a/web/viewer.html +++ b/web/viewer.html @@ -81,7 +81,7 @@ Next - + @@ -119,7 +119,7 @@ Zoom In - + - - - PDF Viewer - PDFJSSCRIPT_VERSION - - - {ec8030f7-c20a-464f-9b0e-13a3a9e97384} - @FIREFOX_VERSION@ - @FIREFOX_VERSION@ - - - true - true - Mozilla - Uses HTML5 to display PDF files directly in Firefox. - https://support.mozilla.org/kb/Opening%20PDF%20files%20within%20Firefox - 2 - - diff --git a/make.js b/make.js index 7862b1dc5..e5202b296 100755 --- a/make.js +++ b/make.js @@ -9,7 +9,11 @@ var ROOT_DIR = __dirname + '/', // absolute path to project's root LOCALE_SRC_DIR = 'l10n/', GH_PAGES_DIR = BUILD_DIR + 'gh-pages/', REPO = 'git@github.com:mozilla/pdf.js.git', - PYTHON_BIN = 'python2.7'; + PYTHON_BIN = 'python2.7', + MOZCENTRAL_PREF_PREFIX = 'pdfjs', + FIREFOX_PREF_PREFIX = 'extensions.uriloader@pdf.js', + MOZCENTRAL_STREAM_CONVERTER_ID = 'd0c5195d-e798-49d4-b1d3-9324328b2291', + FIREFOX_STREAM_CONVERTER_ID = '6457a96b-2d68-439a-bcfa-44465fbcdbb1'; // // make all @@ -348,6 +352,9 @@ target.firefox = function() { sed('-i', /PDFJSSCRIPT_VERSION/, EXTENSION_VERSION, FIREFOX_BUILD_DIR + '/install.rdf'); sed('-i', /PDFJSSCRIPT_VERSION/, EXTENSION_VERSION, FIREFOX_BUILD_DIR + '/update.rdf'); + sed('-i', /PDFJSSCRIPT_STREAM_CONVERTER_ID/, FIREFOX_STREAM_CONVERTER_ID, FIREFOX_BUILD_DIR + 'components/PdfStreamConverter.js'); + sed('-i', /PDFJSSCRIPT_PREF_PREFIX/, FIREFOX_PREF_PREFIX, FIREFOX_BUILD_DIR + 'components/PdfStreamConverter.js'); + // Update localized metadata var localizedMetadata = cat(EXTENSION_SRC_DIR + '/firefox/metadata.inc'); sed('-i', /.*PDFJS_LOCALIZED_METADATA.*\n/, localizedMetadata, FIREFOX_BUILD_DIR + '/install.rdf'); @@ -383,20 +390,17 @@ target.mozcentral = function() { MOZCENTRAL_TEST_DIR = MOZCENTRAL_EXTENSION_DIR + 'test/', FIREFOX_CONTENT_DIR = EXTENSION_SRC_DIR + '/firefox/content/', FIREFOX_EXTENSION_FILES_TO_COPY = - ['*.js', + ['components/*.js', '*.svg', '*.png', '*.manifest', - 'install.rdf.in', 'README.mozilla', 'components', '../../LICENSE'], DEFAULT_LOCALE_FILES = [LOCALE_SRC_DIR + 'en-US/viewer.properties'], FIREFOX_MC_EXTENSION_FILES = - ['icon.png', - 'icon64.png', - 'chrome.manifest', + ['chrome.manifest', 'components', 'content', 'LICENSE']; @@ -448,9 +452,11 @@ target.mozcentral = function() { cp(DEFAULT_LOCALE_FILES, MOZCENTRAL_L10N_DIR); // Update the build version number - sed('-i', /PDFJSSCRIPT_VERSION/, EXTENSION_VERSION, MOZCENTRAL_EXTENSION_DIR + 'install.rdf.in'); sed('-i', /PDFJSSCRIPT_VERSION/, EXTENSION_VERSION, MOZCENTRAL_EXTENSION_DIR + 'README.mozilla'); + sed('-i', /PDFJSSCRIPT_STREAM_CONVERTER_ID/, MOZCENTRAL_STREAM_CONVERTER_ID, MOZCENTRAL_EXTENSION_DIR + 'components/PdfStreamConverter.js'); + sed('-i', /PDFJSSCRIPT_PREF_PREFIX/, MOZCENTRAL_PREF_PREFIX, MOZCENTRAL_EXTENSION_DIR + 'components/PdfStreamConverter.js'); + // List all files for mozilla-central cd(MOZCENTRAL_EXTENSION_DIR); var extensionFiles = ''; From ee54c0bfe4c80faa99f09efeb00f072a7e3b3492 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Wed, 23 May 2012 16:16:48 -0700 Subject: [PATCH 36/42] Remove disable option. Change download wording to open. --- extensions/firefox/components/PdfStreamConverter.js | 11 +---------- l10n/en-US/chrome.properties | 3 +-- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/extensions/firefox/components/PdfStreamConverter.js b/extensions/firefox/components/PdfStreamConverter.js index ced6b1048..d4a375ccc 100644 --- a/extensions/firefox/components/PdfStreamConverter.js +++ b/extensions/firefox/components/PdfStreamConverter.js @@ -176,20 +176,11 @@ ChromeActions.prototype = { var win = Services.wm.getMostRecentWindow('navigator:browser'); var notificationBox = win.gBrowser.getNotificationBox(); var buttons = [{ - label: getLocalizedString(strings, 'download_document'), + label: getLocalizedString(strings, 'open_with_different_viewer'), accessKey: null, callback: function() { self.download(url); } - }, { - label: getLocalizedString(strings, 'disable_viewer'), - accessKey: null, - callback: function() { - AddonManager.getAddonByID(EXT_ID, function(aAddon) { - aAddon.userDisabled = true; - win.gBrowser.contentWindow.location.reload(); - }); - } }]; notificationBox.appendNotification(message, 'pdfjs-fallback', null, notificationBox.PRIORITY_WARNING_LOW, diff --git a/l10n/en-US/chrome.properties b/l10n/en-US/chrome.properties index f0f4bcb88..26e46bac2 100644 --- a/l10n/en-US/chrome.properties +++ b/l10n/en-US/chrome.properties @@ -1,3 +1,2 @@ unsupported_feature=An unsupported feature was detected in this PDF document. -download_document=Download Document -disable_viewer=Disable Mozilla PDF Viewer +open_with_different_viewer=Open With Different Viewer From 5d67183b4d251d929752b2b39377c94fd7f9c07f Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Wed, 23 May 2012 16:54:55 -0700 Subject: [PATCH 37/42] Fix notification from showing up in wrong window. --- .../firefox/components/PdfStreamConverter.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/extensions/firefox/components/PdfStreamConverter.js b/extensions/firefox/components/PdfStreamConverter.js index d4a375ccc..550fbf271 100644 --- a/extensions/firefox/components/PdfStreamConverter.js +++ b/extensions/firefox/components/PdfStreamConverter.js @@ -100,7 +100,8 @@ function getLocalizedString(strings, id) { } // All the priviledged actions. -function ChromeActions() { +function ChromeActions(domWindow) { + this.domWindow = domWindow; } ChromeActions.prototype = { @@ -170,11 +171,15 @@ ChromeActions.prototype = { return getBoolPref(EXT_PREFIX + '.pdfBugEnabled', false); }, fallback: function(url) { - var strings = getLocalizedStrings('chrome.properties'); var self = this; + var domWindow = this.domWindow; + var strings = getLocalizedStrings('chrome.properties'); var message = getLocalizedString(strings, 'unsupported_feature'); + var win = Services.wm.getMostRecentWindow('navigator:browser'); - var notificationBox = win.gBrowser.getNotificationBox(); + var browser = win.gBrowser.getBrowserForDocument(domWindow.top.document); + var notificationBox = win.gBrowser.getNotificationBox(browser); + var buttons = [{ label: getLocalizedString(strings, 'open_with_different_viewer'), accessKey: null, @@ -290,7 +295,8 @@ PdfStreamConverter.prototype = { var domWindow = getDOMWindow(channel); // Double check the url is still the correct one. if (domWindow.document.documentURIObject.equals(aRequest.URI)) { - let requestListener = new RequestListener(new ChromeActions); + let requestListener = new RequestListener( + new ChromeActions(domWindow)); domWindow.addEventListener(PDFJS_EVENT_ID, function(event) { requestListener.receive(event); }, false, true); From 7ea8caeb18457bc569c4aabeb748e1554d9fc329 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Fri, 25 May 2012 13:03:13 -0700 Subject: [PATCH 38/42] Change unsupported feature message. --- l10n/en-US/chrome.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/l10n/en-US/chrome.properties b/l10n/en-US/chrome.properties index 26e46bac2..467d5920b 100644 --- a/l10n/en-US/chrome.properties +++ b/l10n/en-US/chrome.properties @@ -1,2 +1,2 @@ -unsupported_feature=An unsupported feature was detected in this PDF document. +unsupported_feature=This PDF document might not be displayed correctly. open_with_different_viewer=Open With Different Viewer From 5416b92392f38412960f08fc58ea6c75e56efaf4 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Fri, 25 May 2012 13:07:07 -0700 Subject: [PATCH 39/42] Add todo comment. --- src/evaluator.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/evaluator.js b/src/evaluator.js index 128b82381..55af857e5 100644 --- a/src/evaluator.js +++ b/src/evaluator.js @@ -444,6 +444,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { case 'SA': case 'AIS': case 'TK': + // TODO implement these operators. info('graphic state operator ' + key); break; default: From b1bf3ae56a5494cf3e65ffa8c1019240e2110fc8 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Fri, 25 May 2012 13:22:43 -0700 Subject: [PATCH 40/42] Trigger todo on forms. --- web/viewer.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/web/viewer.js b/web/viewer.js index 0dfc678f9..d88e16e4e 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -932,6 +932,9 @@ var PageView = function pageView(container, pdfPage, id, scale, if (comment) div.appendChild(comment); break; + case 'Widget': + TODO('support forms'); + break; } } }); From c0cfb486213d37820327f9c8042ca8d36e157fd9 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Fri, 25 May 2012 14:52:00 -0700 Subject: [PATCH 41/42] Prevent fallback when not ff extension. --- web/viewer.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web/viewer.js b/web/viewer.js index d88e16e4e..2b9067719 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -391,7 +391,9 @@ var PDFView = { } }, - fallback: function pdfViewDownload() { + fallback: function pdfViewFallback() { + if (!PDFJS.isFirefoxExtension) + return; // Only trigger the fallback once so we don't spam the user with messages // for one PDF. if (this.fellback) From ff14136fabed5d91846f85f137f71aeabf5eaeb8 Mon Sep 17 00:00:00 2001 From: Tomer Cohen Date: Tue, 29 May 2012 01:23:45 +0300 Subject: [PATCH 42/42] Hebrew (he) localization --- l10n/he/metadata.inc | 8 +++++++ l10n/he/viewer.properties | 46 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 l10n/he/metadata.inc create mode 100644 l10n/he/viewer.properties diff --git a/l10n/he/metadata.inc b/l10n/he/metadata.inc new file mode 100644 index 000000000..3493c0b7c --- /dev/null +++ b/l10n/he/metadata.inc @@ -0,0 +1,8 @@ + + + he + קורא PDF + הצגת קבצי PDF ישירות ב־Firefox באמצעות HTML5. + + + diff --git a/l10n/he/viewer.properties b/l10n/he/viewer.properties new file mode 100644 index 000000000..974343e04 --- /dev/null +++ b/l10n/he/viewer.properties @@ -0,0 +1,46 @@ +bookmark.title=דף נוכחי (העתקה או פתיחה בחלון חדש) +previous.title=דף קודם +next.title=דף הבא +print.title=הדפסה +download.title=הורדה +zoom_out.title=התרחקות +zoom_in.title=התקרבות +error_more_info=יותר מידע +error_less_info=פחות מידע +error_close=סגירה +error_build=בניית PDF.JS‏: {{build}} +error_message=הודעה: {{message}} +error_stack=מחסנית: {{stack}} +error_file=קובץ: {{file}} +error_line=שורה: {{line}} +page_scale_width=רוחב דף +page_scale_fit=גודל דף +page_scale_auto=התקרבות אוטומטית +page_scale_actual=גודל אמיתי +toggle_slider.title=מתג החלקה +thumbs.title=הצגת תמונות ממוזערות +outline.title=הצגת מתאר מסמך +loading=בטעינה... {{percent}}% +loading_error_indicator=שגיאה +loading_error=אירעה שגיאה בעת טעינת קובץ PDF. +rendering_error=אירעה שגיאה בעת עיבוד הדף. +page_label=דף: +page_of=מתוך {{pageCount}} +no_outline=אין מתאר זמין +open_file.title=פתיחת קובץ +text_annotation_type=[{{type}} Annotation] +toggle_slider_label=מתג החלקה +thumbs_label=תמונות ממוזערות +outline_label=מתאר מסמך +bookmark_label=תצוגה נוכחית +previous_label=קודם +next_label=הבא +print_label=הדפסה +download_label=הורדה +zoom_out_label=התרחקות +zoom_in_label=התקרבות +zoom.title=מרחק מתצוגה +thumb_page_title=דף {{page}} +thumb_page_canvas=תמונה ממוזערת של דף {{page}} +request_password=קובץ PDF מוגן בססמה: +open_file_label=פתיחה