Skip to content

Commit e11e811

Browse files
committed
Merge pull request mozilla#1943 from yurydelendik/loadingbychrome
Implements loading PDF data by extension/chrome
2 parents 639774d + 077b19f commit e11e811

File tree

2 files changed

+199
-23
lines changed

2 files changed

+199
-23
lines changed

extensions/firefox/components/PdfStreamConverter.js

Lines changed: 155 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const MOZ_CENTRAL = PDFJSSCRIPT_MOZ_CENTRAL;
1414
const PDFJS_EVENT_ID = 'pdf.js.message';
1515
const PDF_CONTENT_TYPE = 'application/pdf';
1616
const PREF_PREFIX = 'PDFJSSCRIPT_PREF_PREFIX';
17+
const PDF_VIEWER_WEB_PAGE = 'resource://pdf.js/web/viewer.html';
1718
const MAX_DATABASE_LENGTH = 4096;
1819
const FIREFOX_ID = '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}';
1920
const SEAMONKEY_ID = '{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}';
@@ -122,9 +123,68 @@ function getLocalizedString(strings, id, property) {
122123
return id;
123124
}
124125

126+
// PDF data storage
127+
function PdfDataListener(length) {
128+
this.length = length; // less than 0, if length is unknown
129+
this.data = new Uint8Array(length >= 0 ? length : 0x10000);
130+
this.loaded = 0;
131+
}
132+
133+
PdfDataListener.prototype = {
134+
set: function PdfDataListener_set(chunk, offset) {
135+
if (this.length < 0) {
136+
var willBeLoaded = this.loaded + chunk.length;
137+
// data length is unknown and new chunk will not fit in the existing
138+
// buffer, resizing the buffer by doubling the its last length
139+
if (this.data.length < willBeLoaded) {
140+
var newLength = this.data.length;
141+
for (; newLength < willBeLoaded; newLength *= 2) {}
142+
var newData = new Uint8Array(newLength);
143+
newData.set(this.data);
144+
this.data = newData;
145+
}
146+
this.data.set(chunk, this.loaded);
147+
this.loaded = willBeLoaded;
148+
} else {
149+
this.data.set(chunk, offset);
150+
this.loaded = offset + chunk.length;
151+
this.onprogress(this.loaded, this.length);
152+
}
153+
},
154+
getData: function PdfDataListener_getData() {
155+
var data = this.length >= 0 ? this.data :
156+
this.data.subarray(0, this.loaded);
157+
delete this.data; // releasing temporary storage
158+
return data;
159+
},
160+
finish: function PdfDataListener_finish() {
161+
this.isDataReady = true;
162+
if (this.oncompleteCallback) {
163+
this.oncompleteCallback(this.getData());
164+
}
165+
},
166+
error: function PdfDataListener_error(errorCode) {
167+
this.errorCode = errorCode;
168+
if (this.oncompleteCallback) {
169+
this.oncompleteCallback(null, errorCode);
170+
}
171+
},
172+
onprogress: function() {},
173+
set oncomplete(value) {
174+
this.oncompleteCallback = value;
175+
if (this.isDataReady) {
176+
value(this.getData());
177+
}
178+
if (this.errorCode) {
179+
value(null, this.errorCode);
180+
}
181+
}
182+
};
183+
125184
// All the priviledged actions.
126-
function ChromeActions(domWindow) {
185+
function ChromeActions(domWindow, dataListener) {
127186
this.domWindow = domWindow;
187+
this.dataListener = dataListener;
128188
}
129189

130190
ChromeActions.prototype = {
@@ -194,6 +254,38 @@ ChromeActions.prototype = {
194254
getLocale: function() {
195255
return getStringPref('general.useragent.locale', 'en-US');
196256
},
257+
getLoadingType: function() {
258+
return this.dataListener ? 'passive' : 'active';
259+
},
260+
initPassiveLoading: function() {
261+
if (!this.dataListener)
262+
return false;
263+
264+
var domWindow = this.domWindow;
265+
this.dataListener.onprogress =
266+
function ChromeActions_dataListenerProgress(loaded, total) {
267+
268+
domWindow.postMessage({
269+
pdfjsLoadAction: 'progress',
270+
loaded: loaded,
271+
total: total
272+
}, '*');
273+
};
274+
275+
this.dataListener.oncomplete =
276+
function ChromeActions_dataListenerComplete(data, errorCode) {
277+
278+
domWindow.postMessage({
279+
pdfjsLoadAction: 'complete',
280+
data: data,
281+
errorCode: errorCode
282+
}, '*');
283+
284+
delete this.dataListener;
285+
};
286+
287+
return true;
288+
},
197289
getStrings: function(data) {
198290
try {
199291
// Lazy initialization of localizedStrings
@@ -341,42 +433,64 @@ PdfStreamConverter.prototype = {
341433
asyncConvertData: function(aFromType, aToType, aListener, aCtxt) {
342434
if (!isEnabled())
343435
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
344-
// Ignoring HTTP POST requests -- pdf.js has to repeat the request.
345-
var skipConversion = false;
346-
try {
347-
var request = aCtxt;
348-
request.QueryInterface(Ci.nsIHttpChannel);
349-
skipConversion = (request.requestMethod !== 'GET');
350-
} catch (e) {
351-
// Non-HTTP request... continue normally.
436+
437+
var useFetchByChrome = getBoolPref(PREF_PREFIX + '.fetchByChrome', true);
438+
if (!useFetchByChrome) {
439+
// Ignoring HTTP POST requests -- pdf.js has to repeat the request.
440+
var skipConversion = false;
441+
try {
442+
var request = aCtxt;
443+
request.QueryInterface(Ci.nsIHttpChannel);
444+
skipConversion = (request.requestMethod !== 'GET');
445+
} catch (e) {
446+
// Non-HTTP request... continue normally.
447+
}
448+
if (skipConversion)
449+
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
352450
}
353-
if (skipConversion)
354-
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
355451

356452
// Store the listener passed to us
357453
this.listener = aListener;
358454
},
359455

360456
// nsIStreamListener::onDataAvailable
361457
onDataAvailable: function(aRequest, aContext, aInputStream, aOffset, aCount) {
362-
// Do nothing since all the data loading is handled by the viewer.
363-
log('SANITY CHECK: onDataAvailable SHOULD NOT BE CALLED!');
458+
if (!this.dataListener) {
459+
// Do nothing since all the data loading is handled by the viewer.
460+
return;
461+
}
462+
463+
var binaryStream = this.binaryStream;
464+
binaryStream.setInputStream(aInputStream);
465+
this.dataListener.set(binaryStream.readByteArray(aCount), aOffset);
364466
},
365467

366468
// nsIRequestObserver::onStartRequest
367469
onStartRequest: function(aRequest, aContext) {
368470

369471
// Setup the request so we can use it below.
370472
aRequest.QueryInterface(Ci.nsIChannel);
371-
// Cancel the request so the viewer can handle it.
372-
aRequest.cancel(Cr.NS_BINDING_ABORTED);
473+
var useFetchByChrome = getBoolPref(PREF_PREFIX + '.fetchByChrome', true);
474+
var dataListener;
475+
if (useFetchByChrome) {
476+
// Creating storage for PDF data
477+
var contentLength = aRequest.contentLength;
478+
dataListener = new PdfDataListener(contentLength);
479+
this.dataListener = dataListener;
480+
this.binaryStream = Cc['@mozilla.org/binaryinputstream;1']
481+
.createInstance(Ci.nsIBinaryInputStream);
482+
} else {
483+
// Cancel the request so the viewer can handle it.
484+
aRequest.cancel(Cr.NS_BINDING_ABORTED);
485+
}
373486

374487
// Create a new channel that is viewer loaded as a resource.
375488
var ioService = Services.io;
376489
var channel = ioService.newChannel(
377-
'resource://pdf.js/web/viewer.html', null, null);
490+
PDF_VIEWER_WEB_PAGE, null, null);
378491

379492
var listener = this.listener;
493+
var self = this;
380494
// Proxy all the request observer calls, when it gets to onStopRequest
381495
// we can get the dom window.
382496
var proxy = {
@@ -390,8 +504,8 @@ PdfStreamConverter.prototype = {
390504
var domWindow = getDOMWindow(channel);
391505
// Double check the url is still the correct one.
392506
if (domWindow.document.documentURIObject.equals(aRequest.URI)) {
393-
let requestListener = new RequestListener(
394-
new ChromeActions(domWindow));
507+
let actions = new ChromeActions(domWindow, dataListener);
508+
let requestListener = new RequestListener(actions);
395509
domWindow.addEventListener(PDFJS_EVENT_ID, function(event) {
396510
requestListener.receive(event);
397511
}, false, true);
@@ -403,11 +517,33 @@ PdfStreamConverter.prototype = {
403517
// Keep the URL the same so the browser sees it as the same.
404518
channel.originalURI = aRequest.URI;
405519
channel.asyncOpen(proxy, aContext);
520+
if (useFetchByChrome) {
521+
// We can use resource principal when data is fetched by the chrome
522+
// e.g. useful for NoScript
523+
var securityManager = Cc['@mozilla.org/scriptsecuritymanager;1']
524+
.getService(Ci.nsIScriptSecurityManager);
525+
var uri = ioService.newURI(PDF_VIEWER_WEB_PAGE, null, null);
526+
// FF16 and below had getCodebasePrincipal (bug 774585)
527+
var resourcePrincipal = 'getSimpleCodebasePrincipal' in securityManager ?
528+
securityManager.getSimpleCodebasePrincipal(uri) :
529+
securityManager.getCodebasePrincipal(uri);
530+
channel.owner = resourcePrincipal;
531+
}
406532
},
407533

408534
// nsIRequestObserver::onStopRequest
409535
onStopRequest: function(aRequest, aContext, aStatusCode) {
410-
// Do nothing.
536+
if (!this.dataListener) {
537+
// Do nothing
538+
return;
539+
}
540+
541+
if (Components.isSuccessCode(aStatusCode))
542+
this.dataListener.finish();
543+
else
544+
this.dataListener.error(aStatusCode);
545+
delete this.dataListener;
546+
delete this.binaryStream;
411547
}
412548
};
413549

web/viewer.js

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -348,11 +348,42 @@ var PDFView = {
348348
return support;
349349
},
350350

351+
initPassiveLoading: function pdfViewInitPassiveLoading() {
352+
if (!PDFView.loadingBar) {
353+
PDFView.loadingBar = new ProgressBar('#loadingBar', {});
354+
}
355+
356+
window.addEventListener('message', function window_message(e) {
357+
var args = e.data;
358+
359+
if (typeof args !== 'object' || !('pdfjsLoadAction' in args))
360+
return;
361+
switch (args.pdfjsLoadAction) {
362+
case 'progress':
363+
PDFView.progress(args.loaded / args.total);
364+
break;
365+
case 'complete':
366+
if (!args.data) {
367+
PDFView.error(mozL10n.get('loading_error', null,
368+
'An error occurred while loading the PDF.'), e);
369+
break;
370+
}
371+
PDFView.open(args.data, 0);
372+
break;
373+
}
374+
});
375+
FirefoxCom.requestSync('initPassiveLoading', null);
376+
},
377+
378+
setTitleUsingUrl: function pdfViewSetTitleUsingUrl(url) {
379+
this.url = url;
380+
document.title = decodeURIComponent(getFileName(url)) || url;
381+
},
382+
351383
open: function pdfViewOpen(url, scale, password) {
352384
var parameters = {password: password};
353385
if (typeof url === 'string') { // URL
354-
this.url = url;
355-
document.title = decodeURIComponent(getFileName(url)) || url;
386+
this.setTitleUsingUrl(url);
356387
parameters.url = url;
357388
} else if (url && 'byteLength' in url) { // ArrayBuffer
358389
parameters.data = url;
@@ -1768,7 +1799,7 @@ var TextLayerBuilder = function textLayerBuilder(textLayerDiv) {
17681799
};
17691800
};
17701801

1771-
window.addEventListener('load', function webViewerLoad(evt) {
1802+
document.addEventListener('DOMContentLoaded', function webViewerLoad(evt) {
17721803
PDFView.initialize();
17731804
var params = PDFView.parseQueryString(document.location.search.substring(1));
17741805

@@ -1870,6 +1901,15 @@ window.addEventListener('load', function webViewerLoad(evt) {
18701901
PDFView.sidebarOpen = outerContainer.classList.contains('sidebarOpen');
18711902
PDFView.renderHighestPriority();
18721903
});
1904+
1905+
//#if (FIREFOX || MOZCENTRAL)
1906+
//if (FirefoxCom.requestSync('getLoadingType') == 'passive') {
1907+
// PDFView.setTitleUsingUrl(file);
1908+
// PDFView.initPassiveLoading();
1909+
// return;
1910+
//}
1911+
//#endif
1912+
18731913
//#if !B2G
18741914
PDFView.open(file, 0);
18751915
//#endif
@@ -1961,7 +2001,7 @@ window.addEventListener('change', function webViewerChange(evt) {
19612001

19622002
var file = files[0];
19632003
fileReader.readAsArrayBuffer(file);
1964-
document.title = file.name;
2004+
PDFView.setTitleUsingUrl(file.name);
19652005

19662006
// URL does not reflect proper document location - hiding some icons.
19672007
document.getElementById('viewBookmark').setAttribute('hidden', 'true');

0 commit comments

Comments
 (0)