diff --git a/extensions/firefox/bootstrap.js b/extensions/firefox/bootstrap.js index bbc53195e..06fcf182f 100644 --- a/extensions/firefox/bootstrap.js +++ b/extensions/firefox/bootstrap.js @@ -3,17 +3,155 @@ 'use strict'; +const EXT_PREFIX = 'extensions.uriloader@pdf.js'; +const PDFJS_EVENT_ID = 'pdf.js.message'; let Cc = Components.classes; let Ci = Components.interfaces; let Cm = Components.manager; let Cu = Components.utils; +let application = Cc['@mozilla.org/fuel/application;1'] + .getService(Ci.fuelIApplication); +let privateBrowsing = Cc['@mozilla.org/privatebrowsing;1'] + .getService(Ci.nsIPrivateBrowsingService); Cu.import('resource://gre/modules/Services.jsm'); function log(str) { dump(str + '\n'); } +// watchWindows() and unload() are from Ed Lee's examples at +// https://github.com/Mardak/restartless/blob/watchWindows/bootstrap.js +/** + * Apply a callback to each open and new browser windows. + * + * @param {function} callback 1-parameter function that gets a browser window. + */ +function watchWindows(callback) { + // Wrap the callback in a function that ignores failures + function watcher(window) { + try { + // Now that the window has loaded, only handle browser windows + let {documentElement} = window.document; + if (documentElement.getAttribute('windowtype') == 'navigator:browser') + callback(window); + } + catch (ex) {} + } + // Wait for the window to finish loading before running the callback + function runOnLoad(window) { + // Listen for one load event before checking the window type + window.addEventListener('load', function runOnce() { + window.removeEventListener('load', runOnce, false); + watcher(window); + }, false); + } + + // Add functionality to existing windows + let windows = Services.wm.getEnumerator(null); + while (windows.hasMoreElements()) { + // Only run the watcher immediately if the window is completely loaded + let window = windows.getNext(); + if (window.document.readyState == 'complete') + watcher(window); + // Wait for the window to load before continuing + else + runOnLoad(window); + } + + // Watch for new browser windows opening then wait for it to load + function windowWatcher(subject, topic) { + if (topic == 'domwindowopened') + runOnLoad(subject); + } + Services.ww.registerNotification(windowWatcher); + + // Make sure to stop watching for windows if we're unloading + unload(function() Services.ww.unregisterNotification(windowWatcher)); +} + +/** + * Save callbacks to run when unloading. Optionally scope the callback to a + * container, e.g., window. Provide a way to run all the callbacks. + * + * @param {function} callback 0-parameter function to call on unload. + * @param {node} container Remove the callback when this container unloads. + * @return {function} A 0-parameter function that undoes adding the callback. + */ +function unload(callback, container) { + // Initialize the array of unloaders on the first usage + let unloaders = unload.unloaders; + if (unloaders == null) + unloaders = unload.unloaders = []; + + // Calling with no arguments runs all the unloader callbacks + if (callback == null) { + unloaders.slice().forEach(function(unloader) unloader()); + unloaders.length = 0; + return; + } + + // The callback is bound to the lifetime of the container if we have one + if (container != null) { + // Remove the unloader when the container unloads + container.addEventListener('unload', removeUnloader, false); + + // Wrap the callback to additionally remove the unload listener + let origCallback = callback; + callback = function() { + container.removeEventListener('unload', removeUnloader, false); + origCallback(); + } + } + + // Wrap the callback in a function that ignores failures + function unloader() { + try { + callback(); + } + catch (ex) {} + } + unloaders.push(unloader); + + // Provide a way to remove the unloader + function removeUnloader() { + let index = unloaders.indexOf(unloader); + if (index != -1) + unloaders.splice(index, 1); + } + return removeUnloader; +} + +function messageCallback(event) { + log(event.target.ownerDocument.currentScript); + var message = event.target, doc = message.ownerDocument; + var inPrivateBrowswing = privateBrowsing.privateBrowsingEnabled; + // Verify the message came from a PDF. + // TODO + var action = message.getUserData('action'); + var data = message.getUserData('data'); + switch (action) { + case 'download': + Services.wm.getMostRecentWindow('navigator:browser').saveURL(data); + break; + case 'setDatabase': + if (inPrivateBrowswing) + return; + application.prefs.setValue(EXT_PREFIX + '.database', data); + break; + case 'getDatabase': + var response; + if (inPrivateBrowswing) + response = '{}'; + else + response = application.prefs.getValue(EXT_PREFIX + '.database', '{}'); + message.setUserData('response', response, null); + break; + } +} + + +// All the boostrap functions: function startup(aData, aReason) { let manifestPath = 'chrome.manifest'; let manifest = Cc['@mozilla.org/file/local;1'] @@ -26,11 +164,22 @@ function startup(aData, aReason) { } catch (e) { log(e); } + + watchWindows(function(window) { + window.addEventListener(PDFJS_EVENT_ID, messageCallback, false, true); + unload(function() { + window.removeEventListener(PDFJS_EVENT_ID, messageCallback, false, true); + }); + }); } function shutdown(aData, aReason) { - if (Services.prefs.getBoolPref('extensions.pdf.js.active')) + if (Services.prefs.getBoolPref('extensions.pdf.js.active')) { Services.prefs.setBoolPref('extensions.pdf.js.active', false); + // Clean up with unloaders when we're deactivating + if (aReason != APP_SHUTDOWN) + unload(); + } } function install(aData, aReason) { @@ -39,5 +188,6 @@ function install(aData, aReason) { function uninstall(aData, aReason) { Services.prefs.clearUserPref('extensions.pdf.js.active'); + application.prefs.setValue(EXT_PREFIX + '.database', '{}'); } diff --git a/extensions/firefox/components/pdfContentHandler.js b/extensions/firefox/components/pdfContentHandler.js index 320d69d30..edd8ee3e0 100644 --- a/extensions/firefox/components/pdfContentHandler.js +++ b/extensions/firefox/components/pdfContentHandler.js @@ -20,7 +20,7 @@ function log(aMsg) { dump(msg + '\n'); } -const NS_ERROR_WONT_HANDLE_CONTENT = 0x805d0001; +const NS_ERROR_NOT_IMPLEMENTED = 0x80004001; function pdfContentHandler() { } @@ -51,11 +51,13 @@ pdfContentHandler.prototype = { // nsIStreamConverter::convert convert: function(aFromStream, aFromType, aToType, aCtxt) { - return aFromStream; + return aFromStream; }, // nsIStreamConverter::asyncConvertData asyncConvertData: function(aFromType, aToType, aListener, aCtxt) { + if (!Services.prefs.getBoolPref('extensions.pdf.js.active')) + throw NS_ERROR_NOT_IMPLEMENTED; // Store the listener passed to us this.listener = aListener; }, @@ -73,15 +75,6 @@ pdfContentHandler.prototype = { // Cancel the request so the viewer can handle it. aRequest.cancel(Cr.NS_BINDING_ABORTED); - // Check if we should download. - var targetUrl = aRequest.originalURI.spec; - var downloadHash = targetUrl.indexOf('?#pdfjs.action=download'); - if (downloadHash >= 0) { - targetUrl = targetUrl.substring(0, downloadHash); - Services.wm.getMostRecentWindow("navigator:browser").saveURL(targetUrl); - return; - } - // Create a new channel that is viewer loaded as a resource. var ioService = Cc['@mozilla.org/network/io-service;1'] .getService(Ci.nsIIOService); diff --git a/web/viewer.js b/web/viewer.js index 55d0a595c..790b5ae63 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -61,6 +61,31 @@ var RenderingQueue = (function RenderingQueueClosure() { return RenderingQueue; })(); +var FirefoxCom = (function FirefoxComClosure() { + return { + /** + * Creates an event that hopefully the extension is listening for and will + * synchronously respond to. + * @param {String} action The action to trigger. + * @param {String} data Optional data to send. + * @return {*} The response. + */ + request: function(action, data) { + var request = document.createTextNode(''); + request.setUserData('action', action, null); + request.setUserData('data', data, null); + document.documentElement.appendChild(request); + + var sender = document.createEvent('Events'); + sender.initEvent('pdf.js.message', true, false); + request.dispatchEvent(sender); + var response = request.getUserData('response'); + document.documentElement.removeChild(request); + return response; + } + }; +})(); + // Settings Manager - This is a utility for saving settings // First we see if localStorage is available, FF bug #495747 // If not, we use FUEL in FF @@ -74,10 +99,14 @@ var Settings = (function SettingsClosure() { return true; })(); + var isFirefoxExtension = PDFJS.isFirefoxExtension; + function Settings(fingerprint) { var database = null; var index; - if (isLocalStorageEnabled) + if (isFirefoxExtension) + database = FirefoxCom.request('getDatabase', null); + else if (isLocalStorageEnabled) database = localStorage.getItem('database') || '{}'; else return false; @@ -106,8 +135,11 @@ var Settings = (function SettingsClosure() { set: function settingsSet(name, val) { var file = this.file; file[name] = val; - if (isLocalStorageEnabled) - localStorage.setItem('database', JSON.stringify(this.database)); + var database = JSON.stringify(this.database); + if (isFirefoxExtension) + FirefoxCom.request('setDatabase', database); + else if (isLocalStorageEnabled) + localStorage.setItem('database', database); }, get: function settingsGet(name, defaultValue) { @@ -250,13 +282,12 @@ var PDFView = { download: function pdfViewDownload() { var url = this.url.split('#')[0]; - // For the extension we add an extra '?' to force the page to reload, its - // stripped off by the extension. - if (PDFJS.isFirefoxExtension) - url += '?#pdfjs.action=download'; - else + if (PDFJS.isFirefoxExtension) { + FirefoxCom.request('download', url); + } else { url += '#pdfjs.action=download', '_parent'; - window.open(url, '_parent'); + window.open(url, '_parent'); + } }, navigateTo: function pdfViewNavigateTo(dest) {