16
16
*/
17
17
/* jshint esnext:true */
18
18
/* globals Components, Services, XPCOMUtils, NetUtil, PrivateBrowsingUtils,
19
- dump */
19
+ dump, NetworkManager */
20
20
21
21
'use strict' ;
22
22
@@ -37,6 +37,7 @@ const MAX_DATABASE_LENGTH = 4096;
37
37
Cu .import ('resource://gre/modules/XPCOMUtils.jsm' );
38
38
Cu .import ('resource://gre/modules/Services.jsm' );
39
39
Cu .import ('resource://gre/modules/NetUtil.jsm' );
40
+ Cu .import ('resource://pdf.js/network.js' );
40
41
41
42
XPCOMUtils .defineLazyModuleGetter (this , 'PrivateBrowsingUtils' ,
42
43
'resource://gre/modules/PrivateBrowsingUtils.jsm' );
@@ -190,9 +191,8 @@ PdfDataListener.prototype = {
190
191
};
191
192
192
193
// All the priviledged actions.
193
- function ChromeActions (domWindow , dataListener , contentDispositionFilename ) {
194
+ function ChromeActions (domWindow , contentDispositionFilename ) {
194
195
this .domWindow = domWindow ;
195
- this .dataListener = dataListener ;
196
196
this .contentDispositionFilename = contentDispositionFilename ;
197
197
}
198
198
@@ -305,39 +305,6 @@ ChromeActions.prototype = {
305
305
getLocale : function () {
306
306
return getStringPref ('general.useragent.locale' , 'en-US' );
307
307
},
308
- getLoadingType : function () {
309
- return this .dataListener ? 'passive' : 'active' ;
310
- },
311
- initPassiveLoading : function () {
312
- if (!this .dataListener )
313
- return false ;
314
-
315
- var domWindow = this .domWindow ;
316
- this .dataListener .onprogress =
317
- function ChromeActions_dataListenerProgress (loaded , total ) {
318
-
319
- domWindow .postMessage ({
320
- pdfjsLoadAction : 'progress' ,
321
- loaded : loaded ,
322
- total : total
323
- }, '*' );
324
- };
325
-
326
- var self = this ;
327
- this .dataListener .oncomplete =
328
- function ChromeActions_dataListenerComplete (data , errorCode ) {
329
-
330
- domWindow .postMessage ({
331
- pdfjsLoadAction : 'complete' ,
332
- data : data ,
333
- errorCode : errorCode
334
- }, '*' );
335
-
336
- delete self .dataListener ;
337
- };
338
-
339
- return true ;
340
- },
341
308
getStrings : function (data ) {
342
309
try {
343
310
// Lazy initialization of localizedStrings
@@ -436,6 +403,140 @@ ChromeActions.prototype = {
436
403
}
437
404
};
438
405
406
+ var RangedChromeActions = (function RangedChromeActionsClosure () {
407
+ /**
408
+ * This is for range requests
409
+ */
410
+ function RangedChromeActions (
411
+ domWindow , contentDispositionFilename , originalRequest ) {
412
+
413
+ ChromeActions .call (this , domWindow , contentDispositionFilename );
414
+
415
+ this .pdfUrl = originalRequest .URI .resolve ('');
416
+ this .contentLength = originalRequest .contentLength ;
417
+
418
+ // Pass all the headers from the original request through
419
+ var httpHeaderVisitor = {
420
+ headers : {},
421
+ visitHeader : function (aHeader , aValue ) {
422
+ if (aHeader === 'Range' ) {
423
+ // When loading the PDF from cache, firefox seems to set the Range
424
+ // request header to fetch only the unfetched portions of the file
425
+ // (e.g. 'Range: bytes=1024-'). However, we want to set this header
426
+ // manually to fetch the PDF in chunks.
427
+ return ;
428
+ }
429
+ this .headers [aHeader ] = aValue ;
430
+ }
431
+ };
432
+ originalRequest .visitRequestHeaders (httpHeaderVisitor );
433
+
434
+ var getXhr = function getXhr () {
435
+ const XMLHttpRequest = Components .Constructor (
436
+ '@mozilla.org/xmlextras/xmlhttprequest;1' );
437
+ return new XMLHttpRequest ();
438
+ };
439
+
440
+ this .networkManager = new NetworkManager (this .pdfUrl , {
441
+ httpHeaders : httpHeaderVisitor .headers ,
442
+ getXhr : getXhr
443
+ });
444
+
445
+ var self = this ;
446
+ // If we are in range request mode, this means we manually issued xhr
447
+ // requests, which we need to abort when we leave the page
448
+ domWindow .addEventListener ('unload' , function unload (e ) {
449
+ self .networkManager .abortAllRequests ();
450
+ domWindow .removeEventListener (e .type , unload );
451
+ });
452
+ }
453
+
454
+ RangedChromeActions .prototype = Object .create (ChromeActions .prototype );
455
+ var proto = RangedChromeActions .prototype ;
456
+ proto .constructor = RangedChromeActions ;
457
+
458
+ proto .initPassiveLoading = function RangedChromeActions_initPassiveLoading () {
459
+ this .domWindow .postMessage ({
460
+ pdfjsLoadAction : 'supportsRangedLoading' ,
461
+ pdfUrl : this .pdfUrl ,
462
+ length : this .contentLength
463
+ }, '*' );
464
+
465
+ return true ;
466
+ };
467
+
468
+ proto .requestDataRange = function RangedChromeActions_requestDataRange (args ) {
469
+ var begin = args .begin ;
470
+ var end = args .end ;
471
+ var domWindow = this .domWindow ;
472
+ // TODO(mack): Support error handler. We're not currently not handling
473
+ // errors from chrome code for non-range requests, so this doesn't
474
+ // seem high-pri
475
+ this .networkManager .requestRange (begin , end , {
476
+ onDone : function RangedChromeActions_onDone (args ) {
477
+ domWindow .postMessage ({
478
+ pdfjsLoadAction : 'range' ,
479
+ begin : args .begin ,
480
+ chunk : args .chunk
481
+ }, '*' );
482
+ }
483
+ });
484
+ };
485
+
486
+ return RangedChromeActions ;
487
+ })();
488
+
489
+ var StandardChromeActions = (function StandardChromeActionsClosure () {
490
+
491
+ /**
492
+ * This is for a single network stream
493
+ */
494
+ function StandardChromeActions (domWindow , contentDispositionFilename ,
495
+ dataListener ) {
496
+
497
+ ChromeActions .call (this , domWindow , contentDispositionFilename );
498
+ this .dataListener = dataListener ;
499
+ }
500
+
501
+ StandardChromeActions .prototype = Object .create (ChromeActions .prototype );
502
+ var proto = StandardChromeActions .prototype ;
503
+ proto .constructor = StandardChromeActions ;
504
+
505
+ proto .initPassiveLoading =
506
+ function StandardChromeActions_initPassiveLoading () {
507
+
508
+ if (!this .dataListener ) {
509
+ return false ;
510
+ }
511
+
512
+ var self = this ;
513
+
514
+ this .dataListener .onprogress = function ChromeActions_dataListenerProgress (
515
+ loaded , total ) {
516
+ self .domWindow .postMessage ({
517
+ pdfjsLoadAction : 'progress' ,
518
+ loaded : loaded ,
519
+ total : total
520
+ }, '*' );
521
+ };
522
+
523
+ this .dataListener .oncomplete = function ChromeActions_dataListenerComplete (
524
+ data , errorCode ) {
525
+ self .domWindow .postMessage ({
526
+ pdfjsLoadAction : 'complete' ,
527
+ data : data ,
528
+ errorCode : errorCode
529
+ }, '*' );
530
+
531
+ delete self .dataListener ;
532
+ };
533
+
534
+ return true ;
535
+ };
536
+
537
+ return StandardChromeActions ;
538
+ })();
539
+
439
540
// Event listener to trigger chrome privedged code.
440
541
function RequestListener (actions ) {
441
542
this .actions = actions ;
@@ -552,11 +653,17 @@ PdfStreamConverter.prototype = {
552
653
/*
553
654
* This component works as such:
554
655
* 1. asyncConvertData stores the listener
555
- * 2. onStartRequest creates a new channel, streams the viewer and cancels
556
- * the request so pdf.js can do the request
557
- * Since the request is cancelled onDataAvailable should not be called. The
558
- * onStopRequest does nothing. The convert function just returns the stream,
559
- * it's just the synchronous version of asyncConvertData.
656
+ * 2. onStartRequest creates a new channel, streams the viewer
657
+ * 3. If range requests are supported:
658
+ * 3.1. Suspends and cancels the request so we can issue range
659
+ * requests instead.
660
+ *
661
+ * If range rquests are not supported:
662
+ * 3.1. Read the stream as it's loaded in onDataAvailable to send
663
+ * to the viewer
664
+ *
665
+ * The convert function just returns the stream, it's just the synchronous
666
+ * version of asyncConvertData.
560
667
*/
561
668
562
669
// nsIStreamConverter::convert
@@ -573,40 +680,57 @@ PdfStreamConverter.prototype = {
573
680
// nsIStreamListener::onDataAvailable
574
681
onDataAvailable : function (aRequest , aContext , aInputStream , aOffset , aCount ) {
575
682
if (!this .dataListener ) {
576
- // Do nothing since all the data loading is handled by the viewer.
577
683
return ;
578
684
}
579
685
580
686
var binaryStream = this .binaryStream ;
581
687
binaryStream .setInputStream (aInputStream );
582
- this .dataListener .append (binaryStream .readByteArray (aCount ));
688
+ var chunk = binaryStream .readByteArray (aCount );
689
+ this .dataListener .append (chunk );
583
690
},
584
691
585
692
// nsIRequestObserver::onStartRequest
586
693
onStartRequest : function (aRequest , aContext ) {
587
694
// Setup the request so we can use it below.
695
+ var acceptRanges = false ;
696
+ try {
697
+ aRequest .QueryInterface (Ci .nsIHttpChannel );
698
+ if (aRequest .getResponseHeader ('Accept-Ranges' ) === 'bytes' ) {
699
+ var hash = aRequest .URI .ref ;
700
+ acceptRanges = hash .indexOf ('disableRange=true' ) < 0 ;
701
+ }
702
+ } catch (e ) {}
588
703
aRequest .QueryInterface (Ci .nsIChannel );
704
+
589
705
aRequest .QueryInterface (Ci .nsIWritablePropertyBag );
590
- // Creating storage for PDF data
591
- var contentLength = aRequest .contentLength ;
592
- var dataListener = new PdfDataListener (contentLength );
593
706
var contentDispositionFilename ;
594
707
try {
595
708
contentDispositionFilename = aRequest .contentDispositionFilename ;
596
709
} catch (e ) {}
597
- this .dataListener = dataListener ;
598
- this .binaryStream = Cc ['@mozilla.org/binaryinputstream;1' ]
599
- .createInstance (Ci .nsIBinaryInputStream );
600
710
601
711
// Change the content type so we don't get stuck in a loop.
602
712
aRequest .setProperty ('contentType' , aRequest .contentType );
603
713
aRequest .contentType = 'text/html' ;
604
714
715
+ if (!acceptRanges ) {
716
+ // Creating storage for PDF data
717
+ var contentLength = aRequest .contentLength ;
718
+ this .dataListener = new PdfDataListener (contentLength );
719
+ this .binaryStream = Cc ['@mozilla.org/binaryinputstream;1' ]
720
+ .createInstance (Ci .nsIBinaryInputStream );
721
+ } else {
722
+ // Suspend the request so we're not consuming any of the stream,
723
+ // but we can't cancel the request yet. Otherwise, the original
724
+ // listener will think we do not want to go the new PDF url
725
+ aRequest .suspend ();
726
+ }
727
+
605
728
// Create a new channel that is viewer loaded as a resource.
606
729
var ioService = Services .io ;
607
730
var channel = ioService .newChannel (
608
731
PDF_VIEWER_WEB_PAGE , null , null );
609
732
733
+ var self = this ;
610
734
var listener = this .listener ;
611
735
// Proxy all the request observer calls, when it gets to onStopRequest
612
736
// we can get the dom window. We also intentionally pass on the original
@@ -625,8 +749,18 @@ PdfStreamConverter.prototype = {
625
749
var domWindow = getDOMWindow (channel );
626
750
// Double check the url is still the correct one.
627
751
if (domWindow .document .documentURIObject .equals (aRequest .URI )) {
628
- var actions = new ChromeActions (domWindow , dataListener ,
629
- contentDispositionFilename );
752
+ var actions ;
753
+ if (acceptRanges ) {
754
+ // We are going to be issuing range requests, so cancel the
755
+ // original request
756
+ aRequest .resume ();
757
+ aRequest .cancel (Cr .NS_BINDING_ABORTED );
758
+ actions = new RangedChromeActions (domWindow ,
759
+ contentDispositionFilename , aRequest );
760
+ } else {
761
+ actions = new StandardChromeActions (
762
+ domWindow , contentDispositionFilename , self .dataListener );
763
+ }
630
764
var requestListener = new RequestListener (actions );
631
765
domWindow .addEventListener (PDFJS_EVENT_ID , function (event ) {
632
766
requestListener .receive (event );
0 commit comments