diff --git a/extensions/firefox/components/PdfStreamConverter.js b/extensions/firefox/components/PdfStreamConverter.js index 2c984e86c..2e7ab56e8 100644 --- a/extensions/firefox/components/PdfStreamConverter.js +++ b/extensions/firefox/components/PdfStreamConverter.js @@ -19,6 +19,7 @@ const SEAMONKEY_ID = '{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}'; 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 appInfo = Cc['@mozilla.org/xre/app-info;1'] .getService(Ci.nsIXULAppInfo); @@ -91,9 +92,15 @@ function getLocalizedStrings(path) { } return map; } +function getLocalizedString(strings, id) { + if (id in strings) + return strings[id]['textContent']; + return id; +} // All the priviledged actions. -function ChromeActions() { +function ChromeActions(domWindow) { + this.domWindow = domWindow; } ChromeActions.prototype = { @@ -161,10 +168,30 @@ ChromeActions.prototype = { }, pdfBugEnabled: function() { return getBoolPref(PREF_PREFIX + '.pdfBugEnabled', false); + }, + fallback: function(url) { + 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 browser = win.gBrowser.getBrowserForDocument(domWindow.top.document); + var notificationBox = win.gBrowser.getNotificationBox(browser); + + var buttons = [{ + label: getLocalizedString(strings, 'open_with_different_viewer'), + accessKey: null, + callback: function() { + self.download(url); + } + }]; + notificationBox.appendNotification(message, 'pdfjs-fallback', null, + notificationBox.PRIORITY_WARNING_LOW, + buttons); } }; - // Event listener to trigger chrome privedged code. function RequestListener(actions) { this.actions = actions; @@ -267,7 +294,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); diff --git a/l10n/en-US/chrome.properties b/l10n/en-US/chrome.properties new file mode 100644 index 000000000..467d5920b --- /dev/null +++ b/l10n/en-US/chrome.properties @@ -0,0 +1,2 @@ +unsupported_feature=This PDF document might not be displayed correctly. +open_with_different_viewer=Open With Different Viewer diff --git a/make.js b/make.js index eba7903cc..227abed4b 100755 --- a/make.js +++ b/make.js @@ -110,6 +110,10 @@ target.locale = function() { cp(path + '/viewer.properties', EXTENSION_LOCALE_OUTPUT + '/' + locale); } + if (test('-f', path + '/chrome.properties')) { + cp(path + '/chrome.properties', EXTENSION_LOCALE_OUTPUT + '/' + locale); + } + if (test('-f', path + '/metadata.inc')) { var metadata = cat(path + '/metadata.inc'); metadataContent += metadata; @@ -398,7 +402,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 = ['chrome.manifest', 'components', diff --git a/src/api.js b/src/api.js index 3ac4da565..d9a9faedd 100644 --- a/src/api.js +++ b/src/api.js @@ -452,7 +452,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 bd8161691..bef92422f 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 1a8db1473..55af857e5 100644 --- a/src/evaluator.js +++ b/src/evaluator.js @@ -418,6 +418,18 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { value[1] ]); break; + case 'BM': + // We support the default so don't trigger the TODO. + if (!isName(value) || value.name != 'Normal') + TODO('graphic state operator ' + key); + break; + case 'SMask': + // We support the default so don't trigger the TODO. + if (!isName(value) || value.name != 'None') + TODO('graphic state operator ' + key); + break; + // Only generate info log messages for the following since + // they are unlikey to have a big impact on the rendering. case 'OP': case 'op': case 'OPM': @@ -430,14 +442,13 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { case 'HT': case 'SM': case 'SA': - case 'BM': - case 'SMask': case 'AIS': case 'TK': - TODO('graphic state operator ' + key); + // TODO implement these operators. + info('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 b010bb9ab..1d498746a 100644 --- a/src/fonts.js +++ b/src/fonts.js @@ -3648,7 +3648,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 90e6cee5d..b8359b5d1 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,32 +12,44 @@ function log(msg) { print(msg); } -function warn(msg) { - if (verbosity >= WARNINGS) - log('Warning: ' + 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); + } } -function backtrace() { - try { - throw new Error(); - } catch (e) { - return e.stack ? e.stack.split('\n').slice(2).join('\n') : ''; +// Non-fatal warnings that should trigger the fallback UI. +function warn(msg) { + 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) { - if (verbosity >= TODOS) - log('TODO: ' + what); + warn('TODO: ' + what); } -function malformed(msg) { - error('Malformed PDF: ' + msg); +function backtrace() { + try { + throw new Error(); + } catch (e) { + return e.stack ? e.stack.split('\n').slice(2).join('\n') : ''; + } } function assert(cond, 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 84a8298a7..e362b0a91 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) { @@ -244,6 +247,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 f616b02c9..2b9067719 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'); @@ -390,6 +391,18 @@ var PDFView = { } }, + 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) + return; + this.fellback = true; + var url = this.url.split('#')[0]; + FirefoxCom.request('fallback', url); + }, + navigateTo: function pdfViewNavigateTo(dest) { if (typeof dest === 'string') dest = this.destinations[dest]; @@ -452,6 +465,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'); @@ -478,32 +519,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) { @@ -916,6 +934,9 @@ var PageView = function pageView(container, pdfPage, id, scale, if (comment) div.appendChild(comment); break; + case 'Widget': + TODO('support forms'); + break; } } }); @@ -1397,6 +1418,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);