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.
279 lines
9.2 KiB
279 lines
9.2 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_ID = 'uriloader@pdf.js'; |
|
const EXT_PREFIX = 'extensions.' + EXT_ID; |
|
const MAX_DATABASE_LENGTH = 4096; |
|
|
|
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 privateBrowsing = Cc['@mozilla.org/privatebrowsing;1'] |
|
.getService(Ci.nsIPrivateBrowsingService); |
|
let inPrivateBrowswing = privateBrowsing.privateBrowsingEnabled; |
|
|
|
function getBoolPref(pref, def) { |
|
try { |
|
return Services.prefs.getBoolPref(pref); |
|
} catch (ex) { |
|
return def; |
|
} |
|
} |
|
|
|
function setStringPref(pref, value) { |
|
let str = Cc['@mozilla.org/supports-string;1'] |
|
.createInstance(Ci.nsISupportsString); |
|
str.data = value; |
|
Services.prefs.setComplexValue(pref, Ci.nsISupportsString, str); |
|
} |
|
|
|
function getStringPref(pref, def) { |
|
try { |
|
return Services.prefs.getComplexValue(pref, Ci.nsISupportsString).data; |
|
} catch (ex) { |
|
return def; |
|
} |
|
} |
|
|
|
function log(aMsg) { |
|
if (!getBoolPref(EXT_PREFIX + '.pdfBugEnabled', false)) |
|
return; |
|
let msg = 'PdfStreamConverter.js: ' + (aMsg.join ? aMsg.join('') : aMsg); |
|
Services.console.logStringMessage(msg); |
|
dump(msg + '\n'); |
|
} |
|
|
|
function getDOMWindow(aChannel) { |
|
var requestor = aChannel.notificationCallbacks; |
|
var win = requestor.getInterface(Components.interfaces.nsIDOMWindow); |
|
return win; |
|
} |
|
|
|
// Fake l10n until we get the real l10n sorted out. |
|
var mozL10n = { |
|
get: function(key, args, fallback) { |
|
return fallback; |
|
} |
|
}; |
|
|
|
// All the priviledged actions. |
|
function ChromeActions() { |
|
this.inPrivateBrowswing = privateBrowsing.privateBrowsingEnabled; |
|
} |
|
ChromeActions.prototype = { |
|
download: function(data) { |
|
let mimeService = Cc['@mozilla.org/mime;1'].getService(Ci.nsIMIMEService); |
|
var handlerInfo = mimeService. |
|
getFromTypeAndExtension('application/pdf', 'pdf'); |
|
var uri = NetUtil.newURI(data); |
|
|
|
var extHelperAppSvc = |
|
Cc['@mozilla.org/uriloader/external-helper-app-service;1']. |
|
getService(Ci.nsIExternalHelperAppService); |
|
var frontWindow = Cc['@mozilla.org/embedcomp/window-watcher;1']. |
|
getService(Ci.nsIWindowWatcher).activeWindow; |
|
var ioService = Services.io; |
|
var channel = ioService.newChannel(data, null, null); |
|
var listener = { |
|
extListener: null, |
|
onStartRequest: function(aRequest, aContext) { |
|
this.extListener = extHelperAppSvc.doContent('application/pdf', |
|
aRequest, frontWindow, false); |
|
this.extListener.onStartRequest(aRequest, aContext); |
|
}, |
|
onStopRequest: function(aRequest, aContext, aStatusCode) { |
|
if (this.extListener) |
|
this.extListener.onStopRequest(aRequest, aContext, aStatusCode); |
|
}, |
|
onDataAvailable: function(aRequest, aContext, aInputStream, aOffset, |
|
aCount) { |
|
this.extListener.onDataAvailable(aRequest, aContext, aInputStream, |
|
aOffset, aCount); |
|
} |
|
}; |
|
|
|
channel.asyncOpen(listener, null); |
|
}, |
|
setDatabase: function(data) { |
|
if (this.inPrivateBrowswing) |
|
return; |
|
// Protect against something sending tons of data to setDatabase. |
|
if (data.length > MAX_DATABASE_LENGTH) |
|
return; |
|
setStringPref(EXT_PREFIX + '.database', data); |
|
}, |
|
getDatabase: function() { |
|
if (this.inPrivateBrowswing) |
|
return '{}'; |
|
return getStringPref(EXT_PREFIX + '.database', '{}'); |
|
}, |
|
getLocale: function() { |
|
return getStringPref('general.useragent.locale', 'en-US'); |
|
}, |
|
pdfBugEnabled: function() { |
|
return getBoolPref(EXT_PREFIX + '.pdfBugEnabled', false); |
|
}, |
|
fallback: function(url) { |
|
var self = this; |
|
var message = mozL10n.get('unsupported_feature', null, |
|
'An unsupported feature was detected in this PDF document.'); |
|
var win = Services.wm.getMostRecentWindow('navigator:browser'); |
|
var notificationBox = win.gBrowser.getNotificationBox(); |
|
var buttons = [{ |
|
label: mozL10n.get('download_document', null, 'Download Document'), |
|
accessKey: null, |
|
callback: function() { |
|
self.download(url); |
|
} |
|
}, { |
|
label: mozL10n.get('disable_pdfjs', null, |
|
'Disable Mozilla PDF Viewer'), |
|
accessKey: null, |
|
callback: function() { |
|
AddonManager.getAddonByID(EXT_ID, function(aAddon) { |
|
aAddon.userDisabled = true; |
|
win.gBrowser.contentWindow.location.reload(); |
|
}); |
|
} |
|
}]; |
|
notificationBox.appendNotification(message, 'pdfjs-fallback', null, |
|
notificationBox.PRIORITY_WARNING_LOW, |
|
buttons); |
|
} |
|
}; |
|
|
|
// 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) { |
|
// 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 !== 'GET'); |
|
} 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); |
|
|
|
var listener = this.listener; |
|
// Proxy all the request observer calls, when it gets to onStopRequest |
|
// we can get the dom window. |
|
var proxy = { |
|
onStartRequest: function() { |
|
listener.onStartRequest.apply(listener, arguments); |
|
}, |
|
onDataAvailable: function() { |
|
listener.onDataAvailable.apply(listener, arguments); |
|
}, |
|
onStopRequest: function() { |
|
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); |
|
domWindow.addEventListener(PDFJS_EVENT_ID, function(event) { |
|
requestListener.receive(event); |
|
}, false, true); |
|
} |
|
listener.onStopRequest.apply(listener, arguments); |
|
} |
|
}; |
|
|
|
// Keep the URL the same so the browser sees it as the same. |
|
channel.originalURI = aRequest.URI; |
|
channel.asyncOpen(proxy, aContext); |
|
}, |
|
|
|
// nsIRequestObserver::onStopRequest |
|
onStopRequest: function(aRequest, aContext, aStatusCode) { |
|
// Do nothing. |
|
} |
|
}; |
|
|
|
var NSGetFactory = XPCOMUtils.generateNSGetFactory([PdfStreamConverter]);
|
|
|