You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
203 lines
6.8 KiB
203 lines
6.8 KiB
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ |
|
|
|
'use strict'; |
|
|
|
var EXPORTED_SYMBOLS = ['PdfStreamConverter']; |
|
|
|
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 EXT_PREFIX = 'extensions.uriloader@pdf.js'; |
|
const MAX_DATABASE_LENGTH = 4096; |
|
|
|
Cu.import('resource://gre/modules/XPCOMUtils.jsm'); |
|
Cu.import('resource://gre/modules/Services.jsm'); |
|
|
|
function log(aMsg) { |
|
let msg = 'PdfStreamConverter.js: ' + (aMsg.join ? aMsg.join('') : aMsg); |
|
Services.console.logStringMessage(msg); |
|
dump(msg + '\n'); |
|
} |
|
function getWindow(top, id) { |
|
return top.QueryInterface(Ci.nsIInterfaceRequestor) |
|
.getInterface(Ci.nsIDOMWindowUtils) |
|
.getOuterWindowWithId(id); |
|
} |
|
function windowID(win) { |
|
return win.QueryInterface(Ci.nsIInterfaceRequestor) |
|
.getInterface(Ci.nsIDOMWindowUtils) |
|
.outerWindowID; |
|
} |
|
function topWindow(win) { |
|
return win.QueryInterface(Ci.nsIInterfaceRequestor) |
|
.getInterface(Ci.nsIWebNavigation) |
|
.QueryInterface(Ci.nsIDocShellTreeItem) |
|
.rootTreeItem |
|
.QueryInterface(Ci.nsIInterfaceRequestor) |
|
.getInterface(Ci.nsIDOMWindow); |
|
} |
|
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(data) { |
|
if (this.inPrivateBrowswing) |
|
return; |
|
// Protect against something sending tons of data to setDatabase. |
|
if (data.length > MAX_DATABASE_LENGTH) |
|
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; |
|
} |
|
// Receive an event and synchronously responds. |
|
RequestListener.prototype.receive = 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); |
|
}; |
|
|
|
|
|
function PdfStreamConverter() { |
|
} |
|
|
|
PdfStreamConverter.prototype = { |
|
|
|
// properties required for XPCOM registration: |
|
classID: Components.ID('{6457a96b-2d68-439a-bcfa-44465fbcdbb1}'), |
|
classDescription: 'pdf.js Component', |
|
contractID: '@mozilla.org/streamconv;1?from=application/pdf&to=*/*', |
|
|
|
QueryInterface: XPCOMUtils.generateQI([ |
|
Ci.nsISupports, |
|
Ci.nsIStreamConverter, |
|
Ci.nsIStreamListener, |
|
Ci.nsIRequestObserver |
|
]), |
|
|
|
/* |
|
* This component works as such: |
|
* 1. asyncConvertData stores the listener |
|
* 2. onStartRequest creates a new channel, streams the viewer and cancels |
|
* the request so pdf.js can do the request |
|
* Since the request is cancelled onDataAvailable should not be called. The |
|
* onStopRequest does nothing. The convert function just returns the stream, |
|
* it's just the synchronous version of asyncConvertData. |
|
*/ |
|
|
|
// nsIStreamConverter::convert |
|
convert: function(aFromStream, aFromType, aToType, aCtxt) { |
|
throw Cr.NS_ERROR_NOT_IMPLEMENTED; |
|
}, |
|
|
|
// nsIStreamConverter::asyncConvertData |
|
asyncConvertData: function(aFromType, aToType, aListener, aCtxt) { |
|
if (!Services.prefs.getBoolPref('extensions.pdf.js.active')) |
|
throw Cr.NS_ERROR_NOT_IMPLEMENTED; |
|
|
|
// Ignoring HTTP POST requests -- pdf.js has to repeat the request. |
|
var skipConversion = false; |
|
try { |
|
var request = aCtxt; |
|
request.QueryInterface(Ci.nsIHttpChannel); |
|
skipConversion = (request.requestMethod === 'POST'); |
|
} catch (e) { |
|
// Non-HTTP request... continue normally. |
|
} |
|
if (skipConversion) |
|
throw Cr.NS_ERROR_NOT_IMPLEMENTED; |
|
|
|
// Store the listener passed to us |
|
this.listener = aListener; |
|
}, |
|
|
|
// nsIStreamListener::onDataAvailable |
|
onDataAvailable: function(aRequest, aContext, aInputStream, aOffset, aCount) { |
|
// Do nothing since all the data loading is handled by the viewer. |
|
log('SANITY CHECK: onDataAvailable SHOULD NOT BE CALLED!'); |
|
}, |
|
|
|
// 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. |
|
aRequest.cancel(Cr.NS_BINDING_ABORTED); |
|
|
|
// Create a new channel that is viewer loaded as a resource. |
|
var ioService = Services.io; |
|
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.URI; |
|
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. Code snippet from John Galt. |
|
let window = aRequest.loadGroup.groupObserver |
|
.QueryInterface(Ci.nsIWebProgress) |
|
.DOMWindow; |
|
let top = topWindow(window); |
|
let id = windowID(window); |
|
window = null; |
|
|
|
top.addEventListener('DOMWindowCreated', function onDOMWinCreated(event) { |
|
let doc = event.originalTarget; |
|
let win = doc.defaultView; |
|
|
|
if (id == windowID(win)) { |
|
top.removeEventListener('DOMWindowCreated', onDOMWinCreated, true); |
|
if (!doc.documentURIObject.equals(aRequest.URI)) |
|
return; |
|
|
|
let requestListener = new RequestListener(new ChromeActions); |
|
win.addEventListener(PDFJS_EVENT_ID, function(event) { |
|
requestListener.receive(event); |
|
}, false, true); |
|
} else if (!getWindow(top, id)) { |
|
top.removeEventListener('DOMWindowCreated', onDOMWinCreated, true); |
|
} |
|
}, true); |
|
}, |
|
|
|
// nsIRequestObserver::onStopRequest |
|
onStopRequest: function(aRequest, aContext, aStatusCode) { |
|
// Do nothing. |
|
} |
|
}; |
|
|
|
var NSGetFactory = XPCOMUtils.generateNSGetFactory([PdfStreamConverter]);
|
|
|