Better way to listen to events and verify them.

This commit is contained in:
Brendan Dahl 2012-01-27 10:53:07 -08:00
parent 0d839c1c59
commit e7a0a2e129
2 changed files with 68 additions and 150 deletions

View File

@ -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) {

View File

@ -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;
}
};