Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ |
michael@0 | 3 | /* Copyright 2012 Mozilla Foundation |
michael@0 | 4 | * |
michael@0 | 5 | * Licensed under the Apache License, Version 2.0 (the "License"); |
michael@0 | 6 | * you may not use this file except in compliance with the License. |
michael@0 | 7 | * You may obtain a copy of the License at |
michael@0 | 8 | * |
michael@0 | 9 | * http://www.apache.org/licenses/LICENSE-2.0 |
michael@0 | 10 | * |
michael@0 | 11 | * Unless required by applicable law or agreed to in writing, software |
michael@0 | 12 | * distributed under the License is distributed on an "AS IS" BASIS, |
michael@0 | 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
michael@0 | 14 | * See the License for the specific language governing permissions and |
michael@0 | 15 | * limitations under the License. |
michael@0 | 16 | */ |
michael@0 | 17 | /* jshint esnext:true */ |
michael@0 | 18 | /* globals Components, Services, XPCOMUtils, NetUtil, PrivateBrowsingUtils, |
michael@0 | 19 | dump, NetworkManager, PdfJsTelemetry */ |
michael@0 | 20 | |
michael@0 | 21 | 'use strict'; |
michael@0 | 22 | |
michael@0 | 23 | var EXPORTED_SYMBOLS = ['PdfStreamConverter']; |
michael@0 | 24 | |
michael@0 | 25 | const Cc = Components.classes; |
michael@0 | 26 | const Ci = Components.interfaces; |
michael@0 | 27 | const Cr = Components.results; |
michael@0 | 28 | const Cu = Components.utils; |
michael@0 | 29 | // True only if this is the version of pdf.js that is included with firefox. |
michael@0 | 30 | const MOZ_CENTRAL = JSON.parse('true'); |
michael@0 | 31 | const PDFJS_EVENT_ID = 'pdf.js.message'; |
michael@0 | 32 | const PDF_CONTENT_TYPE = 'application/pdf'; |
michael@0 | 33 | const PREF_PREFIX = 'pdfjs'; |
michael@0 | 34 | const PDF_VIEWER_WEB_PAGE = 'resource://pdf.js/web/viewer.html'; |
michael@0 | 35 | const MAX_NUMBER_OF_PREFS = 50; |
michael@0 | 36 | const MAX_STRING_PREF_LENGTH = 128; |
michael@0 | 37 | |
michael@0 | 38 | Cu.import('resource://gre/modules/XPCOMUtils.jsm'); |
michael@0 | 39 | Cu.import('resource://gre/modules/Services.jsm'); |
michael@0 | 40 | Cu.import('resource://gre/modules/NetUtil.jsm'); |
michael@0 | 41 | |
michael@0 | 42 | XPCOMUtils.defineLazyModuleGetter(this, 'NetworkManager', |
michael@0 | 43 | 'resource://pdf.js/network.js'); |
michael@0 | 44 | |
michael@0 | 45 | XPCOMUtils.defineLazyModuleGetter(this, 'PrivateBrowsingUtils', |
michael@0 | 46 | 'resource://gre/modules/PrivateBrowsingUtils.jsm'); |
michael@0 | 47 | |
michael@0 | 48 | XPCOMUtils.defineLazyModuleGetter(this, 'PdfJsTelemetry', |
michael@0 | 49 | 'resource://pdf.js/PdfJsTelemetry.jsm'); |
michael@0 | 50 | |
michael@0 | 51 | var Svc = {}; |
michael@0 | 52 | XPCOMUtils.defineLazyServiceGetter(Svc, 'mime', |
michael@0 | 53 | '@mozilla.org/mime;1', |
michael@0 | 54 | 'nsIMIMEService'); |
michael@0 | 55 | |
michael@0 | 56 | function getContainingBrowser(domWindow) { |
michael@0 | 57 | return domWindow.QueryInterface(Ci.nsIInterfaceRequestor) |
michael@0 | 58 | .getInterface(Ci.nsIWebNavigation) |
michael@0 | 59 | .QueryInterface(Ci.nsIDocShell) |
michael@0 | 60 | .chromeEventHandler; |
michael@0 | 61 | } |
michael@0 | 62 | |
michael@0 | 63 | function getChromeWindow(domWindow) { |
michael@0 | 64 | return getContainingBrowser(domWindow).ownerDocument.defaultView; |
michael@0 | 65 | } |
michael@0 | 66 | |
michael@0 | 67 | function getFindBar(domWindow) { |
michael@0 | 68 | var browser = getContainingBrowser(domWindow); |
michael@0 | 69 | try { |
michael@0 | 70 | var tabbrowser = browser.getTabBrowser(); |
michael@0 | 71 | var tab = tabbrowser._getTabForBrowser(browser); |
michael@0 | 72 | return tabbrowser.getFindBar(tab); |
michael@0 | 73 | } catch (e) { |
michael@0 | 74 | // FF22 has no _getTabForBrowser, and FF24 has no getFindBar |
michael@0 | 75 | var chromeWindow = browser.ownerDocument.defaultView; |
michael@0 | 76 | return chromeWindow.gFindBar; |
michael@0 | 77 | } |
michael@0 | 78 | } |
michael@0 | 79 | |
michael@0 | 80 | function setBoolPref(pref, value) { |
michael@0 | 81 | Services.prefs.setBoolPref(pref, value); |
michael@0 | 82 | } |
michael@0 | 83 | |
michael@0 | 84 | function getBoolPref(pref, def) { |
michael@0 | 85 | try { |
michael@0 | 86 | return Services.prefs.getBoolPref(pref); |
michael@0 | 87 | } catch (ex) { |
michael@0 | 88 | return def; |
michael@0 | 89 | } |
michael@0 | 90 | } |
michael@0 | 91 | |
michael@0 | 92 | function setIntPref(pref, value) { |
michael@0 | 93 | Services.prefs.setIntPref(pref, value); |
michael@0 | 94 | } |
michael@0 | 95 | |
michael@0 | 96 | function getIntPref(pref, def) { |
michael@0 | 97 | try { |
michael@0 | 98 | return Services.prefs.getIntPref(pref); |
michael@0 | 99 | } catch (ex) { |
michael@0 | 100 | return def; |
michael@0 | 101 | } |
michael@0 | 102 | } |
michael@0 | 103 | |
michael@0 | 104 | function setStringPref(pref, value) { |
michael@0 | 105 | var str = Cc['@mozilla.org/supports-string;1'] |
michael@0 | 106 | .createInstance(Ci.nsISupportsString); |
michael@0 | 107 | str.data = value; |
michael@0 | 108 | Services.prefs.setComplexValue(pref, Ci.nsISupportsString, str); |
michael@0 | 109 | } |
michael@0 | 110 | |
michael@0 | 111 | function getStringPref(pref, def) { |
michael@0 | 112 | try { |
michael@0 | 113 | return Services.prefs.getComplexValue(pref, Ci.nsISupportsString).data; |
michael@0 | 114 | } catch (ex) { |
michael@0 | 115 | return def; |
michael@0 | 116 | } |
michael@0 | 117 | } |
michael@0 | 118 | |
michael@0 | 119 | function log(aMsg) { |
michael@0 | 120 | if (!getBoolPref(PREF_PREFIX + '.pdfBugEnabled', false)) |
michael@0 | 121 | return; |
michael@0 | 122 | var msg = 'PdfStreamConverter.js: ' + (aMsg.join ? aMsg.join('') : aMsg); |
michael@0 | 123 | Services.console.logStringMessage(msg); |
michael@0 | 124 | dump(msg + '\n'); |
michael@0 | 125 | } |
michael@0 | 126 | |
michael@0 | 127 | function getDOMWindow(aChannel) { |
michael@0 | 128 | var requestor = aChannel.notificationCallbacks ? |
michael@0 | 129 | aChannel.notificationCallbacks : |
michael@0 | 130 | aChannel.loadGroup.notificationCallbacks; |
michael@0 | 131 | var win = requestor.getInterface(Components.interfaces.nsIDOMWindow); |
michael@0 | 132 | return win; |
michael@0 | 133 | } |
michael@0 | 134 | |
michael@0 | 135 | function getLocalizedStrings(path) { |
michael@0 | 136 | var stringBundle = Cc['@mozilla.org/intl/stringbundle;1']. |
michael@0 | 137 | getService(Ci.nsIStringBundleService). |
michael@0 | 138 | createBundle('chrome://pdf.js/locale/' + path); |
michael@0 | 139 | |
michael@0 | 140 | var map = {}; |
michael@0 | 141 | var enumerator = stringBundle.getSimpleEnumeration(); |
michael@0 | 142 | while (enumerator.hasMoreElements()) { |
michael@0 | 143 | var string = enumerator.getNext().QueryInterface(Ci.nsIPropertyElement); |
michael@0 | 144 | var key = string.key, property = 'textContent'; |
michael@0 | 145 | var i = key.lastIndexOf('.'); |
michael@0 | 146 | if (i >= 0) { |
michael@0 | 147 | property = key.substring(i + 1); |
michael@0 | 148 | key = key.substring(0, i); |
michael@0 | 149 | } |
michael@0 | 150 | if (!(key in map)) |
michael@0 | 151 | map[key] = {}; |
michael@0 | 152 | map[key][property] = string.value; |
michael@0 | 153 | } |
michael@0 | 154 | return map; |
michael@0 | 155 | } |
michael@0 | 156 | function getLocalizedString(strings, id, property) { |
michael@0 | 157 | property = property || 'textContent'; |
michael@0 | 158 | if (id in strings) |
michael@0 | 159 | return strings[id][property]; |
michael@0 | 160 | return id; |
michael@0 | 161 | } |
michael@0 | 162 | |
michael@0 | 163 | // PDF data storage |
michael@0 | 164 | function PdfDataListener(length) { |
michael@0 | 165 | this.length = length; // less than 0, if length is unknown |
michael@0 | 166 | this.data = new Uint8Array(length >= 0 ? length : 0x10000); |
michael@0 | 167 | this.loaded = 0; |
michael@0 | 168 | } |
michael@0 | 169 | |
michael@0 | 170 | PdfDataListener.prototype = { |
michael@0 | 171 | append: function PdfDataListener_append(chunk) { |
michael@0 | 172 | var willBeLoaded = this.loaded + chunk.length; |
michael@0 | 173 | if (this.length >= 0 && this.length < willBeLoaded) { |
michael@0 | 174 | this.length = -1; // reset the length, server is giving incorrect one |
michael@0 | 175 | } |
michael@0 | 176 | if (this.length < 0 && this.data.length < willBeLoaded) { |
michael@0 | 177 | // data length is unknown and new chunk will not fit in the existing |
michael@0 | 178 | // buffer, resizing the buffer by doubling the its last length |
michael@0 | 179 | var newLength = this.data.length; |
michael@0 | 180 | for (; newLength < willBeLoaded; newLength *= 2) {} |
michael@0 | 181 | var newData = new Uint8Array(newLength); |
michael@0 | 182 | newData.set(this.data); |
michael@0 | 183 | this.data = newData; |
michael@0 | 184 | } |
michael@0 | 185 | this.data.set(chunk, this.loaded); |
michael@0 | 186 | this.loaded = willBeLoaded; |
michael@0 | 187 | this.onprogress(this.loaded, this.length >= 0 ? this.length : void(0)); |
michael@0 | 188 | }, |
michael@0 | 189 | getData: function PdfDataListener_getData() { |
michael@0 | 190 | var data = this.data; |
michael@0 | 191 | if (this.loaded != data.length) |
michael@0 | 192 | data = data.subarray(0, this.loaded); |
michael@0 | 193 | delete this.data; // releasing temporary storage |
michael@0 | 194 | return data; |
michael@0 | 195 | }, |
michael@0 | 196 | finish: function PdfDataListener_finish() { |
michael@0 | 197 | this.isDataReady = true; |
michael@0 | 198 | if (this.oncompleteCallback) { |
michael@0 | 199 | this.oncompleteCallback(this.getData()); |
michael@0 | 200 | } |
michael@0 | 201 | }, |
michael@0 | 202 | error: function PdfDataListener_error(errorCode) { |
michael@0 | 203 | this.errorCode = errorCode; |
michael@0 | 204 | if (this.oncompleteCallback) { |
michael@0 | 205 | this.oncompleteCallback(null, errorCode); |
michael@0 | 206 | } |
michael@0 | 207 | }, |
michael@0 | 208 | onprogress: function() {}, |
michael@0 | 209 | get oncomplete() { |
michael@0 | 210 | return this.oncompleteCallback; |
michael@0 | 211 | }, |
michael@0 | 212 | set oncomplete(value) { |
michael@0 | 213 | this.oncompleteCallback = value; |
michael@0 | 214 | if (this.isDataReady) { |
michael@0 | 215 | value(this.getData()); |
michael@0 | 216 | } |
michael@0 | 217 | if (this.errorCode) { |
michael@0 | 218 | value(null, this.errorCode); |
michael@0 | 219 | } |
michael@0 | 220 | } |
michael@0 | 221 | }; |
michael@0 | 222 | |
michael@0 | 223 | // All the priviledged actions. |
michael@0 | 224 | function ChromeActions(domWindow, contentDispositionFilename) { |
michael@0 | 225 | this.domWindow = domWindow; |
michael@0 | 226 | this.contentDispositionFilename = contentDispositionFilename; |
michael@0 | 227 | this.telemetryState = { |
michael@0 | 228 | documentInfo: false, |
michael@0 | 229 | firstPageInfo: false, |
michael@0 | 230 | streamTypesUsed: [], |
michael@0 | 231 | startAt: Date.now() |
michael@0 | 232 | }; |
michael@0 | 233 | } |
michael@0 | 234 | |
michael@0 | 235 | ChromeActions.prototype = { |
michael@0 | 236 | isInPrivateBrowsing: function() { |
michael@0 | 237 | return PrivateBrowsingUtils.isWindowPrivate(this.domWindow); |
michael@0 | 238 | }, |
michael@0 | 239 | download: function(data, sendResponse) { |
michael@0 | 240 | var self = this; |
michael@0 | 241 | var originalUrl = data.originalUrl; |
michael@0 | 242 | // The data may not be downloaded so we need just retry getting the pdf with |
michael@0 | 243 | // the original url. |
michael@0 | 244 | var originalUri = NetUtil.newURI(data.originalUrl); |
michael@0 | 245 | var filename = data.filename; |
michael@0 | 246 | if (typeof filename !== 'string' || |
michael@0 | 247 | (!/\.pdf$/i.test(filename) && !data.isAttachment)) { |
michael@0 | 248 | filename = 'document.pdf'; |
michael@0 | 249 | } |
michael@0 | 250 | var blobUri = data.blobUrl ? NetUtil.newURI(data.blobUrl) : originalUri; |
michael@0 | 251 | var extHelperAppSvc = |
michael@0 | 252 | Cc['@mozilla.org/uriloader/external-helper-app-service;1']. |
michael@0 | 253 | getService(Ci.nsIExternalHelperAppService); |
michael@0 | 254 | var frontWindow = Cc['@mozilla.org/embedcomp/window-watcher;1']. |
michael@0 | 255 | getService(Ci.nsIWindowWatcher).activeWindow; |
michael@0 | 256 | |
michael@0 | 257 | var docIsPrivate = this.isInPrivateBrowsing(); |
michael@0 | 258 | var netChannel = NetUtil.newChannel(blobUri); |
michael@0 | 259 | if ('nsIPrivateBrowsingChannel' in Ci && |
michael@0 | 260 | netChannel instanceof Ci.nsIPrivateBrowsingChannel) { |
michael@0 | 261 | netChannel.setPrivate(docIsPrivate); |
michael@0 | 262 | } |
michael@0 | 263 | NetUtil.asyncFetch(netChannel, function(aInputStream, aResult) { |
michael@0 | 264 | if (!Components.isSuccessCode(aResult)) { |
michael@0 | 265 | if (sendResponse) |
michael@0 | 266 | sendResponse(true); |
michael@0 | 267 | return; |
michael@0 | 268 | } |
michael@0 | 269 | // Create a nsIInputStreamChannel so we can set the url on the channel |
michael@0 | 270 | // so the filename will be correct. |
michael@0 | 271 | var channel = Cc['@mozilla.org/network/input-stream-channel;1']. |
michael@0 | 272 | createInstance(Ci.nsIInputStreamChannel); |
michael@0 | 273 | channel.QueryInterface(Ci.nsIChannel); |
michael@0 | 274 | try { |
michael@0 | 275 | // contentDisposition/contentDispositionFilename is readonly before FF18 |
michael@0 | 276 | channel.contentDisposition = Ci.nsIChannel.DISPOSITION_ATTACHMENT; |
michael@0 | 277 | if (self.contentDispositionFilename) { |
michael@0 | 278 | channel.contentDispositionFilename = self.contentDispositionFilename; |
michael@0 | 279 | } else { |
michael@0 | 280 | channel.contentDispositionFilename = filename; |
michael@0 | 281 | } |
michael@0 | 282 | } catch (e) {} |
michael@0 | 283 | channel.setURI(originalUri); |
michael@0 | 284 | channel.contentStream = aInputStream; |
michael@0 | 285 | if ('nsIPrivateBrowsingChannel' in Ci && |
michael@0 | 286 | channel instanceof Ci.nsIPrivateBrowsingChannel) { |
michael@0 | 287 | channel.setPrivate(docIsPrivate); |
michael@0 | 288 | } |
michael@0 | 289 | |
michael@0 | 290 | var listener = { |
michael@0 | 291 | extListener: null, |
michael@0 | 292 | onStartRequest: function(aRequest, aContext) { |
michael@0 | 293 | this.extListener = extHelperAppSvc.doContent((data.isAttachment ? '' : |
michael@0 | 294 | 'application/pdf'), |
michael@0 | 295 | aRequest, frontWindow, false); |
michael@0 | 296 | this.extListener.onStartRequest(aRequest, aContext); |
michael@0 | 297 | }, |
michael@0 | 298 | onStopRequest: function(aRequest, aContext, aStatusCode) { |
michael@0 | 299 | if (this.extListener) |
michael@0 | 300 | this.extListener.onStopRequest(aRequest, aContext, aStatusCode); |
michael@0 | 301 | // Notify the content code we're done downloading. |
michael@0 | 302 | if (sendResponse) |
michael@0 | 303 | sendResponse(false); |
michael@0 | 304 | }, |
michael@0 | 305 | onDataAvailable: function(aRequest, aContext, aInputStream, aOffset, |
michael@0 | 306 | aCount) { |
michael@0 | 307 | this.extListener.onDataAvailable(aRequest, aContext, aInputStream, |
michael@0 | 308 | aOffset, aCount); |
michael@0 | 309 | } |
michael@0 | 310 | }; |
michael@0 | 311 | |
michael@0 | 312 | channel.asyncOpen(listener, null); |
michael@0 | 313 | }); |
michael@0 | 314 | }, |
michael@0 | 315 | getLocale: function() { |
michael@0 | 316 | return getStringPref('general.useragent.locale', 'en-US'); |
michael@0 | 317 | }, |
michael@0 | 318 | getStrings: function(data) { |
michael@0 | 319 | try { |
michael@0 | 320 | // Lazy initialization of localizedStrings |
michael@0 | 321 | if (!('localizedStrings' in this)) |
michael@0 | 322 | this.localizedStrings = getLocalizedStrings('viewer.properties'); |
michael@0 | 323 | |
michael@0 | 324 | var result = this.localizedStrings[data]; |
michael@0 | 325 | return JSON.stringify(result || null); |
michael@0 | 326 | } catch (e) { |
michael@0 | 327 | log('Unable to retrive localized strings: ' + e); |
michael@0 | 328 | return 'null'; |
michael@0 | 329 | } |
michael@0 | 330 | }, |
michael@0 | 331 | pdfBugEnabled: function() { |
michael@0 | 332 | return getBoolPref(PREF_PREFIX + '.pdfBugEnabled', false); |
michael@0 | 333 | }, |
michael@0 | 334 | supportsIntegratedFind: function() { |
michael@0 | 335 | // Integrated find is only supported when we're not in a frame |
michael@0 | 336 | if (this.domWindow.frameElement !== null) { |
michael@0 | 337 | return false; |
michael@0 | 338 | } |
michael@0 | 339 | // ... and when the new find events code exists. |
michael@0 | 340 | var findBar = getFindBar(this.domWindow); |
michael@0 | 341 | return findBar && ('updateControlState' in findBar); |
michael@0 | 342 | }, |
michael@0 | 343 | supportsDocumentFonts: function() { |
michael@0 | 344 | var prefBrowser = getIntPref('browser.display.use_document_fonts', 1); |
michael@0 | 345 | var prefGfx = getBoolPref('gfx.downloadable_fonts.enabled', true); |
michael@0 | 346 | return (!!prefBrowser && prefGfx); |
michael@0 | 347 | }, |
michael@0 | 348 | supportsDocumentColors: function() { |
michael@0 | 349 | return getBoolPref('browser.display.use_document_colors', true); |
michael@0 | 350 | }, |
michael@0 | 351 | reportTelemetry: function (data) { |
michael@0 | 352 | var probeInfo = JSON.parse(data); |
michael@0 | 353 | switch (probeInfo.type) { |
michael@0 | 354 | case 'documentInfo': |
michael@0 | 355 | if (!this.telemetryState.documentInfo) { |
michael@0 | 356 | PdfJsTelemetry.onDocumentVersion(probeInfo.version | 0); |
michael@0 | 357 | PdfJsTelemetry.onDocumentGenerator(probeInfo.generator | 0); |
michael@0 | 358 | if (probeInfo.formType) { |
michael@0 | 359 | PdfJsTelemetry.onForm(probeInfo.formType === 'acroform'); |
michael@0 | 360 | } |
michael@0 | 361 | this.telemetryState.documentInfo = true; |
michael@0 | 362 | } |
michael@0 | 363 | break; |
michael@0 | 364 | case 'pageInfo': |
michael@0 | 365 | if (!this.telemetryState.firstPageInfo) { |
michael@0 | 366 | var duration = Date.now() - this.telemetryState.startAt; |
michael@0 | 367 | PdfJsTelemetry.onTimeToView(duration); |
michael@0 | 368 | this.telemetryState.firstPageInfo = true; |
michael@0 | 369 | } |
michael@0 | 370 | break; |
michael@0 | 371 | case 'streamInfo': |
michael@0 | 372 | if (!Array.isArray(probeInfo.streamTypes)) { |
michael@0 | 373 | break; |
michael@0 | 374 | } |
michael@0 | 375 | for (var i = 0; i < probeInfo.streamTypes.length; i++) { |
michael@0 | 376 | var streamTypeId = probeInfo.streamTypes[i] | 0; |
michael@0 | 377 | if (streamTypeId >= 0 && streamTypeId < 10 && |
michael@0 | 378 | !this.telemetryState.streamTypesUsed[streamTypeId]) { |
michael@0 | 379 | PdfJsTelemetry.onStreamType(streamTypeId); |
michael@0 | 380 | this.telemetryState.streamTypesUsed[streamTypeId] = true; |
michael@0 | 381 | } |
michael@0 | 382 | } |
michael@0 | 383 | break; |
michael@0 | 384 | } |
michael@0 | 385 | }, |
michael@0 | 386 | fallback: function(args, sendResponse) { |
michael@0 | 387 | var featureId = args.featureId; |
michael@0 | 388 | var url = args.url; |
michael@0 | 389 | |
michael@0 | 390 | var self = this; |
michael@0 | 391 | var domWindow = this.domWindow; |
michael@0 | 392 | var strings = getLocalizedStrings('chrome.properties'); |
michael@0 | 393 | var message; |
michael@0 | 394 | if (featureId === 'forms') { |
michael@0 | 395 | message = getLocalizedString(strings, 'unsupported_feature_forms'); |
michael@0 | 396 | } else { |
michael@0 | 397 | message = getLocalizedString(strings, 'unsupported_feature'); |
michael@0 | 398 | } |
michael@0 | 399 | |
michael@0 | 400 | PdfJsTelemetry.onFallback(); |
michael@0 | 401 | |
michael@0 | 402 | var notificationBox = null; |
michael@0 | 403 | try { |
michael@0 | 404 | // Based on MDN's "Working with windows in chrome code" |
michael@0 | 405 | var mainWindow = domWindow |
michael@0 | 406 | .QueryInterface(Components.interfaces.nsIInterfaceRequestor) |
michael@0 | 407 | .getInterface(Components.interfaces.nsIWebNavigation) |
michael@0 | 408 | .QueryInterface(Components.interfaces.nsIDocShellTreeItem) |
michael@0 | 409 | .rootTreeItem |
michael@0 | 410 | .QueryInterface(Components.interfaces.nsIInterfaceRequestor) |
michael@0 | 411 | .getInterface(Components.interfaces.nsIDOMWindow); |
michael@0 | 412 | var browser = mainWindow.gBrowser |
michael@0 | 413 | .getBrowserForDocument(domWindow.top.document); |
michael@0 | 414 | notificationBox = mainWindow.gBrowser.getNotificationBox(browser); |
michael@0 | 415 | } catch (e) { |
michael@0 | 416 | log('Unable to get a notification box for the fallback message'); |
michael@0 | 417 | return; |
michael@0 | 418 | } |
michael@0 | 419 | |
michael@0 | 420 | // Flag so we don't call the response callback twice, since if the user |
michael@0 | 421 | // clicks open with different viewer both the button callback and |
michael@0 | 422 | // eventCallback will be called. |
michael@0 | 423 | var sentResponse = false; |
michael@0 | 424 | var buttons = [{ |
michael@0 | 425 | label: getLocalizedString(strings, 'open_with_different_viewer'), |
michael@0 | 426 | accessKey: getLocalizedString(strings, 'open_with_different_viewer', |
michael@0 | 427 | 'accessKey'), |
michael@0 | 428 | callback: function() { |
michael@0 | 429 | sentResponse = true; |
michael@0 | 430 | sendResponse(true); |
michael@0 | 431 | } |
michael@0 | 432 | }]; |
michael@0 | 433 | notificationBox.appendNotification(message, 'pdfjs-fallback', null, |
michael@0 | 434 | notificationBox.PRIORITY_INFO_LOW, |
michael@0 | 435 | buttons, |
michael@0 | 436 | function eventsCallback(eventType) { |
michael@0 | 437 | // Currently there is only one event "removed" but if there are any other |
michael@0 | 438 | // added in the future we still only care about removed at the moment. |
michael@0 | 439 | if (eventType !== 'removed') |
michael@0 | 440 | return; |
michael@0 | 441 | // Don't send a response again if we already responded when the button was |
michael@0 | 442 | // clicked. |
michael@0 | 443 | if (!sentResponse) |
michael@0 | 444 | sendResponse(false); |
michael@0 | 445 | }); |
michael@0 | 446 | }, |
michael@0 | 447 | updateFindControlState: function(data) { |
michael@0 | 448 | if (!this.supportsIntegratedFind()) |
michael@0 | 449 | return; |
michael@0 | 450 | // Verify what we're sending to the findbar. |
michael@0 | 451 | var result = data.result; |
michael@0 | 452 | var findPrevious = data.findPrevious; |
michael@0 | 453 | var findPreviousType = typeof findPrevious; |
michael@0 | 454 | if ((typeof result !== 'number' || result < 0 || result > 3) || |
michael@0 | 455 | (findPreviousType !== 'undefined' && findPreviousType !== 'boolean')) { |
michael@0 | 456 | return; |
michael@0 | 457 | } |
michael@0 | 458 | getFindBar(this.domWindow).updateControlState(result, findPrevious); |
michael@0 | 459 | }, |
michael@0 | 460 | setPreferences: function(prefs, sendResponse) { |
michael@0 | 461 | var defaultBranch = Services.prefs.getDefaultBranch(PREF_PREFIX + '.'); |
michael@0 | 462 | var numberOfPrefs = 0; |
michael@0 | 463 | var prefValue, prefName; |
michael@0 | 464 | for (var key in prefs) { |
michael@0 | 465 | if (++numberOfPrefs > MAX_NUMBER_OF_PREFS) { |
michael@0 | 466 | log('setPreferences - Exceeded the maximum number of preferences ' + |
michael@0 | 467 | 'that is allowed to be set at once.'); |
michael@0 | 468 | break; |
michael@0 | 469 | } else if (!defaultBranch.getPrefType(key)) { |
michael@0 | 470 | continue; |
michael@0 | 471 | } |
michael@0 | 472 | prefValue = prefs[key]; |
michael@0 | 473 | prefName = (PREF_PREFIX + '.' + key); |
michael@0 | 474 | switch (typeof prefValue) { |
michael@0 | 475 | case 'boolean': |
michael@0 | 476 | setBoolPref(prefName, prefValue); |
michael@0 | 477 | break; |
michael@0 | 478 | case 'number': |
michael@0 | 479 | setIntPref(prefName, prefValue); |
michael@0 | 480 | break; |
michael@0 | 481 | case 'string': |
michael@0 | 482 | if (prefValue.length > MAX_STRING_PREF_LENGTH) { |
michael@0 | 483 | log('setPreferences - Exceeded the maximum allowed length ' + |
michael@0 | 484 | 'for a string preference.'); |
michael@0 | 485 | } else { |
michael@0 | 486 | setStringPref(prefName, prefValue); |
michael@0 | 487 | } |
michael@0 | 488 | break; |
michael@0 | 489 | } |
michael@0 | 490 | } |
michael@0 | 491 | if (sendResponse) { |
michael@0 | 492 | sendResponse(true); |
michael@0 | 493 | } |
michael@0 | 494 | }, |
michael@0 | 495 | getPreferences: function(prefs, sendResponse) { |
michael@0 | 496 | var defaultBranch = Services.prefs.getDefaultBranch(PREF_PREFIX + '.'); |
michael@0 | 497 | var currentPrefs = {}, numberOfPrefs = 0; |
michael@0 | 498 | var prefValue, prefName; |
michael@0 | 499 | for (var key in prefs) { |
michael@0 | 500 | if (++numberOfPrefs > MAX_NUMBER_OF_PREFS) { |
michael@0 | 501 | log('getPreferences - Exceeded the maximum number of preferences ' + |
michael@0 | 502 | 'that is allowed to be fetched at once.'); |
michael@0 | 503 | break; |
michael@0 | 504 | } else if (!defaultBranch.getPrefType(key)) { |
michael@0 | 505 | continue; |
michael@0 | 506 | } |
michael@0 | 507 | prefValue = prefs[key]; |
michael@0 | 508 | prefName = (PREF_PREFIX + '.' + key); |
michael@0 | 509 | switch (typeof prefValue) { |
michael@0 | 510 | case 'boolean': |
michael@0 | 511 | currentPrefs[key] = getBoolPref(prefName, prefValue); |
michael@0 | 512 | break; |
michael@0 | 513 | case 'number': |
michael@0 | 514 | currentPrefs[key] = getIntPref(prefName, prefValue); |
michael@0 | 515 | break; |
michael@0 | 516 | case 'string': |
michael@0 | 517 | currentPrefs[key] = getStringPref(prefName, prefValue); |
michael@0 | 518 | break; |
michael@0 | 519 | } |
michael@0 | 520 | } |
michael@0 | 521 | if (sendResponse) { |
michael@0 | 522 | sendResponse(JSON.stringify(currentPrefs)); |
michael@0 | 523 | } else { |
michael@0 | 524 | return JSON.stringify(currentPrefs); |
michael@0 | 525 | } |
michael@0 | 526 | } |
michael@0 | 527 | }; |
michael@0 | 528 | |
michael@0 | 529 | var RangedChromeActions = (function RangedChromeActionsClosure() { |
michael@0 | 530 | /** |
michael@0 | 531 | * This is for range requests |
michael@0 | 532 | */ |
michael@0 | 533 | function RangedChromeActions( |
michael@0 | 534 | domWindow, contentDispositionFilename, originalRequest, |
michael@0 | 535 | dataListener) { |
michael@0 | 536 | |
michael@0 | 537 | ChromeActions.call(this, domWindow, contentDispositionFilename); |
michael@0 | 538 | this.dataListener = dataListener; |
michael@0 | 539 | this.originalRequest = originalRequest; |
michael@0 | 540 | |
michael@0 | 541 | this.pdfUrl = originalRequest.URI.spec; |
michael@0 | 542 | this.contentLength = originalRequest.contentLength; |
michael@0 | 543 | |
michael@0 | 544 | // Pass all the headers from the original request through |
michael@0 | 545 | var httpHeaderVisitor = { |
michael@0 | 546 | headers: {}, |
michael@0 | 547 | visitHeader: function(aHeader, aValue) { |
michael@0 | 548 | if (aHeader === 'Range') { |
michael@0 | 549 | // When loading the PDF from cache, firefox seems to set the Range |
michael@0 | 550 | // request header to fetch only the unfetched portions of the file |
michael@0 | 551 | // (e.g. 'Range: bytes=1024-'). However, we want to set this header |
michael@0 | 552 | // manually to fetch the PDF in chunks. |
michael@0 | 553 | return; |
michael@0 | 554 | } |
michael@0 | 555 | this.headers[aHeader] = aValue; |
michael@0 | 556 | } |
michael@0 | 557 | }; |
michael@0 | 558 | originalRequest.visitRequestHeaders(httpHeaderVisitor); |
michael@0 | 559 | |
michael@0 | 560 | var self = this; |
michael@0 | 561 | var xhr_onreadystatechange = function xhr_onreadystatechange() { |
michael@0 | 562 | if (this.readyState === 1) { // LOADING |
michael@0 | 563 | var netChannel = this.channel; |
michael@0 | 564 | if ('nsIPrivateBrowsingChannel' in Ci && |
michael@0 | 565 | netChannel instanceof Ci.nsIPrivateBrowsingChannel) { |
michael@0 | 566 | var docIsPrivate = self.isInPrivateBrowsing(); |
michael@0 | 567 | netChannel.setPrivate(docIsPrivate); |
michael@0 | 568 | } |
michael@0 | 569 | } |
michael@0 | 570 | }; |
michael@0 | 571 | var getXhr = function getXhr() { |
michael@0 | 572 | const XMLHttpRequest = Components.Constructor( |
michael@0 | 573 | '@mozilla.org/xmlextras/xmlhttprequest;1'); |
michael@0 | 574 | var xhr = new XMLHttpRequest(); |
michael@0 | 575 | xhr.addEventListener('readystatechange', xhr_onreadystatechange); |
michael@0 | 576 | return xhr; |
michael@0 | 577 | }; |
michael@0 | 578 | |
michael@0 | 579 | this.networkManager = new NetworkManager(this.pdfUrl, { |
michael@0 | 580 | httpHeaders: httpHeaderVisitor.headers, |
michael@0 | 581 | getXhr: getXhr |
michael@0 | 582 | }); |
michael@0 | 583 | |
michael@0 | 584 | // If we are in range request mode, this means we manually issued xhr |
michael@0 | 585 | // requests, which we need to abort when we leave the page |
michael@0 | 586 | domWindow.addEventListener('unload', function unload(e) { |
michael@0 | 587 | self.networkManager.abortAllRequests(); |
michael@0 | 588 | domWindow.removeEventListener(e.type, unload); |
michael@0 | 589 | }); |
michael@0 | 590 | } |
michael@0 | 591 | |
michael@0 | 592 | RangedChromeActions.prototype = Object.create(ChromeActions.prototype); |
michael@0 | 593 | var proto = RangedChromeActions.prototype; |
michael@0 | 594 | proto.constructor = RangedChromeActions; |
michael@0 | 595 | |
michael@0 | 596 | proto.initPassiveLoading = function RangedChromeActions_initPassiveLoading() { |
michael@0 | 597 | this.originalRequest.cancel(Cr.NS_BINDING_ABORTED); |
michael@0 | 598 | this.originalRequest = null; |
michael@0 | 599 | this.domWindow.postMessage({ |
michael@0 | 600 | pdfjsLoadAction: 'supportsRangedLoading', |
michael@0 | 601 | pdfUrl: this.pdfUrl, |
michael@0 | 602 | length: this.contentLength, |
michael@0 | 603 | data: this.dataListener.getData() |
michael@0 | 604 | }, '*'); |
michael@0 | 605 | this.dataListener = null; |
michael@0 | 606 | |
michael@0 | 607 | return true; |
michael@0 | 608 | }; |
michael@0 | 609 | |
michael@0 | 610 | proto.requestDataRange = function RangedChromeActions_requestDataRange(args) { |
michael@0 | 611 | var begin = args.begin; |
michael@0 | 612 | var end = args.end; |
michael@0 | 613 | var domWindow = this.domWindow; |
michael@0 | 614 | // TODO(mack): Support error handler. We're not currently not handling |
michael@0 | 615 | // errors from chrome code for non-range requests, so this doesn't |
michael@0 | 616 | // seem high-pri |
michael@0 | 617 | this.networkManager.requestRange(begin, end, { |
michael@0 | 618 | onDone: function RangedChromeActions_onDone(args) { |
michael@0 | 619 | domWindow.postMessage({ |
michael@0 | 620 | pdfjsLoadAction: 'range', |
michael@0 | 621 | begin: args.begin, |
michael@0 | 622 | chunk: args.chunk |
michael@0 | 623 | }, '*'); |
michael@0 | 624 | }, |
michael@0 | 625 | onProgress: function RangedChromeActions_onProgress(evt) { |
michael@0 | 626 | domWindow.postMessage({ |
michael@0 | 627 | pdfjsLoadAction: 'rangeProgress', |
michael@0 | 628 | loaded: evt.loaded, |
michael@0 | 629 | }, '*'); |
michael@0 | 630 | } |
michael@0 | 631 | }); |
michael@0 | 632 | }; |
michael@0 | 633 | |
michael@0 | 634 | return RangedChromeActions; |
michael@0 | 635 | })(); |
michael@0 | 636 | |
michael@0 | 637 | var StandardChromeActions = (function StandardChromeActionsClosure() { |
michael@0 | 638 | |
michael@0 | 639 | /** |
michael@0 | 640 | * This is for a single network stream |
michael@0 | 641 | */ |
michael@0 | 642 | function StandardChromeActions(domWindow, contentDispositionFilename, |
michael@0 | 643 | dataListener) { |
michael@0 | 644 | |
michael@0 | 645 | ChromeActions.call(this, domWindow, contentDispositionFilename); |
michael@0 | 646 | this.dataListener = dataListener; |
michael@0 | 647 | } |
michael@0 | 648 | |
michael@0 | 649 | StandardChromeActions.prototype = Object.create(ChromeActions.prototype); |
michael@0 | 650 | var proto = StandardChromeActions.prototype; |
michael@0 | 651 | proto.constructor = StandardChromeActions; |
michael@0 | 652 | |
michael@0 | 653 | proto.initPassiveLoading = |
michael@0 | 654 | function StandardChromeActions_initPassiveLoading() { |
michael@0 | 655 | |
michael@0 | 656 | if (!this.dataListener) { |
michael@0 | 657 | return false; |
michael@0 | 658 | } |
michael@0 | 659 | |
michael@0 | 660 | var self = this; |
michael@0 | 661 | |
michael@0 | 662 | this.dataListener.onprogress = function ChromeActions_dataListenerProgress( |
michael@0 | 663 | loaded, total) { |
michael@0 | 664 | self.domWindow.postMessage({ |
michael@0 | 665 | pdfjsLoadAction: 'progress', |
michael@0 | 666 | loaded: loaded, |
michael@0 | 667 | total: total |
michael@0 | 668 | }, '*'); |
michael@0 | 669 | }; |
michael@0 | 670 | |
michael@0 | 671 | this.dataListener.oncomplete = function ChromeActions_dataListenerComplete( |
michael@0 | 672 | data, errorCode) { |
michael@0 | 673 | self.domWindow.postMessage({ |
michael@0 | 674 | pdfjsLoadAction: 'complete', |
michael@0 | 675 | data: data, |
michael@0 | 676 | errorCode: errorCode |
michael@0 | 677 | }, '*'); |
michael@0 | 678 | |
michael@0 | 679 | delete self.dataListener; |
michael@0 | 680 | }; |
michael@0 | 681 | |
michael@0 | 682 | return true; |
michael@0 | 683 | }; |
michael@0 | 684 | |
michael@0 | 685 | return StandardChromeActions; |
michael@0 | 686 | })(); |
michael@0 | 687 | |
michael@0 | 688 | // Event listener to trigger chrome privedged code. |
michael@0 | 689 | function RequestListener(actions) { |
michael@0 | 690 | this.actions = actions; |
michael@0 | 691 | } |
michael@0 | 692 | // Receive an event and synchronously or asynchronously responds. |
michael@0 | 693 | RequestListener.prototype.receive = function(event) { |
michael@0 | 694 | var message = event.target; |
michael@0 | 695 | var doc = message.ownerDocument; |
michael@0 | 696 | var action = event.detail.action; |
michael@0 | 697 | var data = event.detail.data; |
michael@0 | 698 | var sync = event.detail.sync; |
michael@0 | 699 | var actions = this.actions; |
michael@0 | 700 | if (!(action in actions)) { |
michael@0 | 701 | log('Unknown action: ' + action); |
michael@0 | 702 | return; |
michael@0 | 703 | } |
michael@0 | 704 | if (sync) { |
michael@0 | 705 | var response = actions[action].call(this.actions, data); |
michael@0 | 706 | var detail = event.detail; |
michael@0 | 707 | detail.__exposedProps__ = {response: 'r'}; |
michael@0 | 708 | detail.response = response; |
michael@0 | 709 | } else { |
michael@0 | 710 | var response; |
michael@0 | 711 | if (!event.detail.callback) { |
michael@0 | 712 | doc.documentElement.removeChild(message); |
michael@0 | 713 | response = null; |
michael@0 | 714 | } else { |
michael@0 | 715 | response = function sendResponse(response) { |
michael@0 | 716 | try { |
michael@0 | 717 | var listener = doc.createEvent('CustomEvent'); |
michael@0 | 718 | listener.initCustomEvent('pdf.js.response', true, false, |
michael@0 | 719 | {response: response, |
michael@0 | 720 | __exposedProps__: {response: 'r'}}); |
michael@0 | 721 | return message.dispatchEvent(listener); |
michael@0 | 722 | } catch (e) { |
michael@0 | 723 | // doc is no longer accessible because the requestor is already |
michael@0 | 724 | // gone. unloaded content cannot receive the response anyway. |
michael@0 | 725 | return false; |
michael@0 | 726 | } |
michael@0 | 727 | }; |
michael@0 | 728 | } |
michael@0 | 729 | actions[action].call(this.actions, data, response); |
michael@0 | 730 | } |
michael@0 | 731 | }; |
michael@0 | 732 | |
michael@0 | 733 | // Forwards events from the eventElement to the contentWindow only if the |
michael@0 | 734 | // content window matches the currently selected browser window. |
michael@0 | 735 | function FindEventManager(eventElement, contentWindow, chromeWindow) { |
michael@0 | 736 | this.types = ['find', |
michael@0 | 737 | 'findagain', |
michael@0 | 738 | 'findhighlightallchange', |
michael@0 | 739 | 'findcasesensitivitychange']; |
michael@0 | 740 | this.chromeWindow = chromeWindow; |
michael@0 | 741 | this.contentWindow = contentWindow; |
michael@0 | 742 | this.eventElement = eventElement; |
michael@0 | 743 | } |
michael@0 | 744 | |
michael@0 | 745 | FindEventManager.prototype.bind = function() { |
michael@0 | 746 | var unload = function(e) { |
michael@0 | 747 | this.unbind(); |
michael@0 | 748 | this.contentWindow.removeEventListener(e.type, unload); |
michael@0 | 749 | }.bind(this); |
michael@0 | 750 | this.contentWindow.addEventListener('unload', unload); |
michael@0 | 751 | |
michael@0 | 752 | for (var i = 0; i < this.types.length; i++) { |
michael@0 | 753 | var type = this.types[i]; |
michael@0 | 754 | this.eventElement.addEventListener(type, this, true); |
michael@0 | 755 | } |
michael@0 | 756 | }; |
michael@0 | 757 | |
michael@0 | 758 | FindEventManager.prototype.handleEvent = function(e) { |
michael@0 | 759 | var chromeWindow = this.chromeWindow; |
michael@0 | 760 | var contentWindow = this.contentWindow; |
michael@0 | 761 | // Only forward the events if they are for our dom window. |
michael@0 | 762 | if (chromeWindow.gBrowser.selectedBrowser.contentWindow === contentWindow) { |
michael@0 | 763 | var detail = e.detail; |
michael@0 | 764 | detail.__exposedProps__ = { |
michael@0 | 765 | query: 'r', |
michael@0 | 766 | caseSensitive: 'r', |
michael@0 | 767 | highlightAll: 'r', |
michael@0 | 768 | findPrevious: 'r' |
michael@0 | 769 | }; |
michael@0 | 770 | var forward = contentWindow.document.createEvent('CustomEvent'); |
michael@0 | 771 | forward.initCustomEvent(e.type, true, true, detail); |
michael@0 | 772 | contentWindow.dispatchEvent(forward); |
michael@0 | 773 | e.preventDefault(); |
michael@0 | 774 | } |
michael@0 | 775 | }; |
michael@0 | 776 | |
michael@0 | 777 | FindEventManager.prototype.unbind = function() { |
michael@0 | 778 | for (var i = 0; i < this.types.length; i++) { |
michael@0 | 779 | var type = this.types[i]; |
michael@0 | 780 | this.eventElement.removeEventListener(type, this, true); |
michael@0 | 781 | } |
michael@0 | 782 | }; |
michael@0 | 783 | |
michael@0 | 784 | function PdfStreamConverter() { |
michael@0 | 785 | } |
michael@0 | 786 | |
michael@0 | 787 | PdfStreamConverter.prototype = { |
michael@0 | 788 | |
michael@0 | 789 | // properties required for XPCOM registration: |
michael@0 | 790 | classID: Components.ID('{d0c5195d-e798-49d4-b1d3-9324328b2291}'), |
michael@0 | 791 | classDescription: 'pdf.js Component', |
michael@0 | 792 | contractID: '@mozilla.org/streamconv;1?from=application/pdf&to=*/*', |
michael@0 | 793 | |
michael@0 | 794 | QueryInterface: XPCOMUtils.generateQI([ |
michael@0 | 795 | Ci.nsISupports, |
michael@0 | 796 | Ci.nsIStreamConverter, |
michael@0 | 797 | Ci.nsIStreamListener, |
michael@0 | 798 | Ci.nsIRequestObserver |
michael@0 | 799 | ]), |
michael@0 | 800 | |
michael@0 | 801 | /* |
michael@0 | 802 | * This component works as such: |
michael@0 | 803 | * 1. asyncConvertData stores the listener |
michael@0 | 804 | * 2. onStartRequest creates a new channel, streams the viewer |
michael@0 | 805 | * 3. If range requests are supported: |
michael@0 | 806 | * 3.1. Leave the request open until the viewer is ready to switch to |
michael@0 | 807 | * range requests. |
michael@0 | 808 | * |
michael@0 | 809 | * If range rquests are not supported: |
michael@0 | 810 | * 3.1. Read the stream as it's loaded in onDataAvailable to send |
michael@0 | 811 | * to the viewer |
michael@0 | 812 | * |
michael@0 | 813 | * The convert function just returns the stream, it's just the synchronous |
michael@0 | 814 | * version of asyncConvertData. |
michael@0 | 815 | */ |
michael@0 | 816 | |
michael@0 | 817 | // nsIStreamConverter::convert |
michael@0 | 818 | convert: function(aFromStream, aFromType, aToType, aCtxt) { |
michael@0 | 819 | throw Cr.NS_ERROR_NOT_IMPLEMENTED; |
michael@0 | 820 | }, |
michael@0 | 821 | |
michael@0 | 822 | // nsIStreamConverter::asyncConvertData |
michael@0 | 823 | asyncConvertData: function(aFromType, aToType, aListener, aCtxt) { |
michael@0 | 824 | // Store the listener passed to us |
michael@0 | 825 | this.listener = aListener; |
michael@0 | 826 | }, |
michael@0 | 827 | |
michael@0 | 828 | // nsIStreamListener::onDataAvailable |
michael@0 | 829 | onDataAvailable: function(aRequest, aContext, aInputStream, aOffset, aCount) { |
michael@0 | 830 | if (!this.dataListener) { |
michael@0 | 831 | return; |
michael@0 | 832 | } |
michael@0 | 833 | |
michael@0 | 834 | var binaryStream = this.binaryStream; |
michael@0 | 835 | binaryStream.setInputStream(aInputStream); |
michael@0 | 836 | var chunk = binaryStream.readByteArray(aCount); |
michael@0 | 837 | this.dataListener.append(chunk); |
michael@0 | 838 | }, |
michael@0 | 839 | |
michael@0 | 840 | // nsIRequestObserver::onStartRequest |
michael@0 | 841 | onStartRequest: function(aRequest, aContext) { |
michael@0 | 842 | // Setup the request so we can use it below. |
michael@0 | 843 | var isHttpRequest = false; |
michael@0 | 844 | try { |
michael@0 | 845 | aRequest.QueryInterface(Ci.nsIHttpChannel); |
michael@0 | 846 | isHttpRequest = true; |
michael@0 | 847 | } catch (e) {} |
michael@0 | 848 | |
michael@0 | 849 | var rangeRequest = false; |
michael@0 | 850 | if (isHttpRequest) { |
michael@0 | 851 | var contentEncoding = 'identity'; |
michael@0 | 852 | try { |
michael@0 | 853 | contentEncoding = aRequest.getResponseHeader('Content-Encoding'); |
michael@0 | 854 | } catch (e) {} |
michael@0 | 855 | |
michael@0 | 856 | var acceptRanges; |
michael@0 | 857 | try { |
michael@0 | 858 | acceptRanges = aRequest.getResponseHeader('Accept-Ranges'); |
michael@0 | 859 | } catch (e) {} |
michael@0 | 860 | |
michael@0 | 861 | var hash = aRequest.URI.ref; |
michael@0 | 862 | rangeRequest = contentEncoding === 'identity' && |
michael@0 | 863 | acceptRanges === 'bytes' && |
michael@0 | 864 | aRequest.contentLength >= 0 && |
michael@0 | 865 | hash.indexOf('disableRange=true') < 0; |
michael@0 | 866 | } |
michael@0 | 867 | |
michael@0 | 868 | aRequest.QueryInterface(Ci.nsIChannel); |
michael@0 | 869 | |
michael@0 | 870 | aRequest.QueryInterface(Ci.nsIWritablePropertyBag); |
michael@0 | 871 | |
michael@0 | 872 | var contentDispositionFilename; |
michael@0 | 873 | try { |
michael@0 | 874 | contentDispositionFilename = aRequest.contentDispositionFilename; |
michael@0 | 875 | } catch (e) {} |
michael@0 | 876 | |
michael@0 | 877 | // Change the content type so we don't get stuck in a loop. |
michael@0 | 878 | aRequest.setProperty('contentType', aRequest.contentType); |
michael@0 | 879 | aRequest.contentType = 'text/html'; |
michael@0 | 880 | if (isHttpRequest) { |
michael@0 | 881 | // We trust PDF viewer, using no CSP |
michael@0 | 882 | aRequest.setResponseHeader('Content-Security-Policy', '', false); |
michael@0 | 883 | aRequest.setResponseHeader('Content-Security-Policy-Report-Only', '', |
michael@0 | 884 | false); |
michael@0 | 885 | aRequest.setResponseHeader('X-Content-Security-Policy', '', false); |
michael@0 | 886 | aRequest.setResponseHeader('X-Content-Security-Policy-Report-Only', '', |
michael@0 | 887 | false); |
michael@0 | 888 | } |
michael@0 | 889 | |
michael@0 | 890 | PdfJsTelemetry.onViewerIsUsed(); |
michael@0 | 891 | PdfJsTelemetry.onDocumentSize(aRequest.contentLength); |
michael@0 | 892 | |
michael@0 | 893 | |
michael@0 | 894 | // Creating storage for PDF data |
michael@0 | 895 | var contentLength = aRequest.contentLength; |
michael@0 | 896 | this.dataListener = new PdfDataListener(contentLength); |
michael@0 | 897 | this.binaryStream = Cc['@mozilla.org/binaryinputstream;1'] |
michael@0 | 898 | .createInstance(Ci.nsIBinaryInputStream); |
michael@0 | 899 | |
michael@0 | 900 | // Create a new channel that is viewer loaded as a resource. |
michael@0 | 901 | var ioService = Services.io; |
michael@0 | 902 | var channel = ioService.newChannel( |
michael@0 | 903 | PDF_VIEWER_WEB_PAGE, null, null); |
michael@0 | 904 | |
michael@0 | 905 | var listener = this.listener; |
michael@0 | 906 | var dataListener = this.dataListener; |
michael@0 | 907 | // Proxy all the request observer calls, when it gets to onStopRequest |
michael@0 | 908 | // we can get the dom window. We also intentionally pass on the original |
michael@0 | 909 | // request(aRequest) below so we don't overwrite the original channel and |
michael@0 | 910 | // trigger an assertion. |
michael@0 | 911 | var proxy = { |
michael@0 | 912 | onStartRequest: function(request, context) { |
michael@0 | 913 | listener.onStartRequest(aRequest, context); |
michael@0 | 914 | }, |
michael@0 | 915 | onDataAvailable: function(request, context, inputStream, offset, count) { |
michael@0 | 916 | listener.onDataAvailable(aRequest, context, inputStream, offset, count); |
michael@0 | 917 | }, |
michael@0 | 918 | onStopRequest: function(request, context, statusCode) { |
michael@0 | 919 | // We get the DOM window here instead of before the request since it |
michael@0 | 920 | // may have changed during a redirect. |
michael@0 | 921 | var domWindow = getDOMWindow(channel); |
michael@0 | 922 | var actions; |
michael@0 | 923 | if (rangeRequest) { |
michael@0 | 924 | actions = new RangedChromeActions( |
michael@0 | 925 | domWindow, contentDispositionFilename, aRequest, dataListener); |
michael@0 | 926 | } else { |
michael@0 | 927 | actions = new StandardChromeActions( |
michael@0 | 928 | domWindow, contentDispositionFilename, dataListener); |
michael@0 | 929 | } |
michael@0 | 930 | var requestListener = new RequestListener(actions); |
michael@0 | 931 | domWindow.addEventListener(PDFJS_EVENT_ID, function(event) { |
michael@0 | 932 | requestListener.receive(event); |
michael@0 | 933 | }, false, true); |
michael@0 | 934 | if (actions.supportsIntegratedFind()) { |
michael@0 | 935 | var chromeWindow = getChromeWindow(domWindow); |
michael@0 | 936 | var findBar = getFindBar(domWindow); |
michael@0 | 937 | var findEventManager = new FindEventManager(findBar, |
michael@0 | 938 | domWindow, |
michael@0 | 939 | chromeWindow); |
michael@0 | 940 | findEventManager.bind(); |
michael@0 | 941 | } |
michael@0 | 942 | listener.onStopRequest(aRequest, context, statusCode); |
michael@0 | 943 | } |
michael@0 | 944 | }; |
michael@0 | 945 | |
michael@0 | 946 | // Keep the URL the same so the browser sees it as the same. |
michael@0 | 947 | channel.originalURI = aRequest.URI; |
michael@0 | 948 | channel.loadGroup = aRequest.loadGroup; |
michael@0 | 949 | |
michael@0 | 950 | // We can use resource principal when data is fetched by the chrome |
michael@0 | 951 | // e.g. useful for NoScript |
michael@0 | 952 | var securityManager = Cc['@mozilla.org/scriptsecuritymanager;1'] |
michael@0 | 953 | .getService(Ci.nsIScriptSecurityManager); |
michael@0 | 954 | var uri = ioService.newURI(PDF_VIEWER_WEB_PAGE, null, null); |
michael@0 | 955 | // FF16 and below had getCodebasePrincipal, it was replaced by |
michael@0 | 956 | // getNoAppCodebasePrincipal (bug 758258). |
michael@0 | 957 | var resourcePrincipal = 'getNoAppCodebasePrincipal' in securityManager ? |
michael@0 | 958 | securityManager.getNoAppCodebasePrincipal(uri) : |
michael@0 | 959 | securityManager.getCodebasePrincipal(uri); |
michael@0 | 960 | aRequest.owner = resourcePrincipal; |
michael@0 | 961 | channel.asyncOpen(proxy, aContext); |
michael@0 | 962 | }, |
michael@0 | 963 | |
michael@0 | 964 | // nsIRequestObserver::onStopRequest |
michael@0 | 965 | onStopRequest: function(aRequest, aContext, aStatusCode) { |
michael@0 | 966 | if (!this.dataListener) { |
michael@0 | 967 | // Do nothing |
michael@0 | 968 | return; |
michael@0 | 969 | } |
michael@0 | 970 | |
michael@0 | 971 | if (Components.isSuccessCode(aStatusCode)) |
michael@0 | 972 | this.dataListener.finish(); |
michael@0 | 973 | else |
michael@0 | 974 | this.dataListener.error(aStatusCode); |
michael@0 | 975 | delete this.dataListener; |
michael@0 | 976 | delete this.binaryStream; |
michael@0 | 977 | } |
michael@0 | 978 | }; |