Commit f96af43d authored by Robert Knight's avatar Robert Knight

Support PDF.js >= 2.5.207

Since [1] PDF.js does not dispatch events to the DOM any more. Therefore
the client needs to listen for the `documentloaded` event from
`PDFApplicationViewer.eventBus`. The `eventBus` property is only
available once the viewer is initialized, so the client needs to wait
for `initializedPromise` to resolve before checking it.

[1] https://github.com/mozilla/pdf.js/pull/11655
parent cb8d3802
...@@ -42,19 +42,32 @@ export default class PDFMetadata { ...@@ -42,19 +42,32 @@ export default class PDFMetadata {
const finish = () => { const finish = () => {
window.removeEventListener('documentload', finish); window.removeEventListener('documentload', finish);
window.removeEventListener('documentloaded', finish); window.removeEventListener('documentloaded', finish);
app.eventBus?.off('documentloaded', finish);
resolve(app); resolve(app);
}; };
if (app.downloadComplete) { if (app.downloadComplete) {
resolve(app); resolve(app);
} else { } else {
// Listen for either the `documentload` (older PDF.js) or // Listen for "documentloaded" event which signals that the document
// `documentloaded` (newer PDF.js) events which signal that the document
// has been downloaded and the first page has been rendered. // has been downloaded and the first page has been rendered.
//
// Newer versions of PDF.js (>= v2.5.207) report events only via
// the PDFViewerApplication's own event bus.
app.initializedPromise?.then(() => {
app.eventBus?.on('documentloaded', finish);
});
// Older versions of PDF.js (< v2.5.207) dispatch events to the DOM
// instead or as well.
// PDF.js >= v2.0.943.
// See https://github.com/mozilla/pdf.js/commit/7bc4bfcc8b7f52b14107f0a551becdf01643c5c2 // See https://github.com/mozilla/pdf.js/commit/7bc4bfcc8b7f52b14107f0a551becdf01643c5c2
window.addEventListener('documentload', finish);
window.addEventListener('documentloaded', finish); window.addEventListener('documentloaded', finish);
// PDF.js < v2.0.943.
window.addEventListener('documentload', finish);
} }
}); });
} }
......
import EventEmitter from 'tiny-emitter';
import PDFMetadata from '../pdf-metadata'; import PDFMetadata from '../pdf-metadata';
/** /**
...@@ -41,11 +43,22 @@ class FakePDFViewerApplication { ...@@ -41,11 +43,22 @@ class FakePDFViewerApplication {
* Initialize the "PDF viewer" as it would be when loading a document or * Initialize the "PDF viewer" as it would be when loading a document or
* when a document fails to load. * when a document fails to load.
*/ */
constructor(url = '') { constructor(url = '', { domEvents = false, eventBusEvents = true } = {}) {
this.url = url; this.url = url;
this.documentInfo = undefined; this.documentInfo = undefined;
this.metadata = undefined; this.metadata = undefined;
this.pdfDocument = null; this.pdfDocument = null;
this.dispatchDOMEvents = domEvents;
// Use `EventEmitter` as a fake version of PDF.js's `EventBus` class as the
// API for subscribing to events is the same.
if (eventBusEvents) {
this.eventBus = new EventEmitter();
}
this.initializedPromise = new Promise(resolve => {
this._resolveInitializedPromise = resolve;
});
} }
/** /**
...@@ -58,10 +71,6 @@ class FakePDFViewerApplication { ...@@ -58,10 +71,6 @@ class FakePDFViewerApplication {
title, title,
eventName = 'documentload', eventName = 'documentload',
}) { }) {
const event = document.createEvent('Event');
event.initEvent(eventName, false, false);
window.dispatchEvent(event);
this.url = url; this.url = url;
this.downloadComplete = true; this.downloadComplete = true;
this.documentInfo = {}; this.documentInfo = {};
...@@ -75,29 +84,69 @@ class FakePDFViewerApplication { ...@@ -75,29 +84,69 @@ class FakePDFViewerApplication {
} }
this.pdfDocument = new FakePDFDocumentProxy({ fingerprint }); this.pdfDocument = new FakePDFDocumentProxy({ fingerprint });
if (this.dispatchDOMEvents) {
const event = document.createEvent('Event');
event.initEvent(eventName, false, false);
window.dispatchEvent(event);
}
this.eventBus?.emit(eventName);
} }
/**
* Simulate PDF viewer initialization completing.
*
* At this point the event bus becomes available.
*/
completeInit() {
this._resolveInitializedPromise();
}
}
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
} }
describe('annotator/plugin/pdf-metadata', function () { describe('annotator/plugin/pdf-metadata', function () {
[ [
// Event dispatched in older PDF.js versions (pre-7bc4bfcc). {
'documentload', // Oldest PDF.js versions (pre-2.x)
// Event dispatched in newer PDF.js versions (post-7bc4bfcc). eventName: 'documentload',
'documentloaded', domEvents: true,
].forEach(eventName => { eventBusEvents: false,
it('waits for the PDF to load before returning metadata', function () { },
const fakeApp = new FakePDFViewerApplication(); {
// Newer PDF.js versions (~ < 2.5.x)
eventName: 'documentloaded',
domEvents: true,
eventBusEvents: false,
},
{
// Current PDF.js versions (>= 2.5.x)
eventName: 'documentloaded',
domEvents: false,
eventBusEvents: true,
},
].forEach(({ eventName, domEvents = false, eventBusEvents = false }, i) => {
it(`waits for PDF to load (${i})`, async () => {
const fakeApp = new FakePDFViewerApplication('', {
domEvents,
eventBusEvents,
});
const pdfMetadata = new PDFMetadata(fakeApp); const pdfMetadata = new PDFMetadata(fakeApp);
fakeApp.completeInit();
// Give `PDFMetadata` a chance to register the "documentloaded" event listener.
await delay(0);
fakeApp.finishLoading({ fakeApp.finishLoading({
eventName, eventName,
url: 'http://fake.com', url: 'http://fake.com',
fingerprint: 'fakeFingerprint', fingerprint: 'fakeFingerprint',
}); });
return pdfMetadata.getUri().then(function (uri) { assert.equal(await pdfMetadata.getUri(), 'http://fake.com/');
assert.equal(uri, 'http://fake.com/');
});
}); });
}); });
......
...@@ -67,17 +67,30 @@ ...@@ -67,17 +67,30 @@
* @prop {(page: number) => PDFPageView|null} getPageView * @prop {(page: number) => PDFPageView|null} getPageView
*/ */
/**
* Defined in `web/ui_utils.js` in the PDF.js source.
*
* @typedef EventBus
* @prop {(event: string, listener: Function) => void} on
* @prop {(event: string, listener: Function) => void} off
*/
/** /**
* The `PDFViewerApplication` global which is the entry-point for accessing PDF.js. * The `PDFViewerApplication` global which is the entry-point for accessing PDF.js.
* *
* Defined in `web/app.js` in the PDF.js source. * Defined in `web/app.js` in the PDF.js source.
* *
* @typedef PDFViewerApplication * @typedef PDFViewerApplication
* @prop {EventBus} [eventBus] -
* Global event bus. Since v1.6.210.
* @prop {PDFDocument} pdfDocument * @prop {PDFDocument} pdfDocument
* @prop {PDFViewer} pdfViewer * @prop {PDFViewer} pdfViewer
* @prop {boolean} downloadComplete * @prop {boolean} downloadComplete
* @prop {PDFDocumentInfo} documentInfo * @prop {PDFDocumentInfo} documentInfo
* @prop {Metadata} metadata * @prop {Metadata} metadata
* @prop {Promise<void>} [initializedPromise] -
* Promise that resolves when PDF.js is initialized. Since v2.4.456.
* See https://github.com/mozilla/pdf.js/wiki/Third-party-viewer-usage#initialization-promise.
*/ */
/** /**
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment