From e7a0a2e1291fb744cdfa769fa3a8409d246078da Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Fri, 27 Jan 2012 10:53:07 -0800 Subject: [PATCH] Better way to listen to events and verify them. --- extensions/firefox/bootstrap.js | 148 +----------------- .../firefox/components/pdfContentHandler.js | 70 ++++++++- 2 files changed, 68 insertions(+), 150 deletions(-) diff --git a/extensions/firefox/bootstrap.js b/extensions/firefox/bootstrap.js index 06fcf182f..f1a712c0c 100644 --- a/extensions/firefox/bootstrap.js +++ b/extensions/firefox/bootstrap.js @@ -9,149 +9,14 @@ 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'] @@ -164,22 +29,11 @@ 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) { diff --git a/extensions/firefox/components/pdfContentHandler.js b/extensions/firefox/components/pdfContentHandler.js index edd8ee3e0..1c6c72c81 100644 --- a/extensions/firefox/components/pdfContentHandler.js +++ b/extensions/firefox/components/pdfContentHandler.js @@ -7,8 +7,10 @@ const Cc = Components.classes; const Ci = Components.interfaces; const Cr = Components.results; const Cu = Components.utils; - +const PDFJS_EVENT_ID = 'pdf.js.message'; const PDF_CONTENT_TYPE = 'application/pdf'; +const NS_ERROR_NOT_IMPLEMENTED = 0x80004001; +const EXT_PREFIX = 'extensions.uriloader@pdf.js'; Cu.import('resource://gre/modules/XPCOMUtils.jsm'); Cu.import('resource://gre/modules/Services.jsm'); @@ -19,8 +21,50 @@ function log(aMsg) { .logStringMessage(msg); dump(msg + '\n'); } +let application = Cc['@mozilla.org/fuel/application;1'] + .getService(Ci.fuelIApplication); +let privateBrowsing = Cc['@mozilla.org/privatebrowsing;1'] + .getService(Ci.nsIPrivateBrowsingService); +let inPrivateBrowswing = privateBrowsing.privateBrowsingEnabled; + +// All the priviledged actions. +function ChromeActions() { + this.inPrivateBrowswing = privateBrowsing.privateBrowsingEnabled; +} +ChromeActions.prototype = { + download: function(data) { + Services.wm.getMostRecentWindow('navigator:browser').saveURL(data); + }, + setDatabase: function() { + if (this.inPrivateBrowswing) + return; + application.prefs.setValue(EXT_PREFIX + '.database', data); + }, + getDatabase: function() { + if (this.inPrivateBrowswing) + return '{}'; + return application.prefs.getValue(EXT_PREFIX + '.database', '{}'); + } +}; + +// Event listener to trigger chrome privedged code. +function RequestListener(actions) { + this.actions = actions; +} +// Recieves an event and synchronously responds. +RequestListener.prototype.recieve = function(event) { + var message = event.target; + var action = message.getUserData('action'); + var data = message.getUserData('data'); + 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); +}; -const NS_ERROR_NOT_IMPLEMENTED = 0x80004001; function pdfContentHandler() { } @@ -70,6 +114,7 @@ pdfContentHandler.prototype = { // nsIRequestObserver::onStartRequest onStartRequest: function(aRequest, aContext) { + // Setup the request so we can use it below. aRequest.QueryInterface(Ci.nsIChannel); // Cancel the request so the viewer can handle it. @@ -80,15 +125,34 @@ pdfContentHandler.prototype = { .getService(Ci.nsIIOService); var channel = ioService.newChannel( 'resource://pdf.js/web/viewer.html', null, null); + // Keep the URL the same so the browser sees it as the same. channel.originalURI = aRequest.originalURI; channel.asyncOpen(this.listener, aContext); + + // Setup a global listener waiting for the next DOM to be created and verfiy + // that its the one we want by its URL. When the correct DOM is found create + // an event listener on that window for the pdf.js events that require + // chrome priviledges. + var url = aRequest.originalURI.spec; + var gb = Services.wm.getMostRecentWindow('navigator:browser'); + var domListener = function domListener(event) { + var doc = event.originalTarget; + var win = doc.defaultView; + if (doc.location.href === url) { + gb.removeEventListener('DOMContentLoaded', domListener); + var requestListener = new RequestListener(new ChromeActions()); + win.addEventListener(PDFJS_EVENT_ID, function(event) { + requestListener.recieve(event); + }, false, true); + } + }; + gb.addEventListener('DOMContentLoaded', domListener, false); }, // nsIRequestObserver::onStopRequest onStopRequest: function(aRequest, aContext, aStatusCode) { // Do nothing. - return; } };