Unverified Commit c7778409 authored by Robert Knight's avatar Robert Knight Committed by GitHub

Merge pull request #818 from hypothesis/support-newer-pdfjs

Support recent versions of PDF.js
parents 186b59dd d2241159
......@@ -38,13 +38,20 @@ class PDFMetadata {
this._loaded = new Promise(resolve => {
const finish = () => {
window.removeEventListener('documentload', finish);
window.removeEventListener('documentloaded', finish);
resolve(app);
};
if (app.documentFingerprint) {
if (app.downloadComplete) {
resolve(app);
} else {
// Listen for either the `documentload` (older PDF.js) or
// `documentloaded` (newer PDF.js) events which signal that the document
// has been downloaded and the first page has been rendered.
//
// See https://github.com/mozilla/pdf.js/commit/7bc4bfcc8b7f52b14107f0a551becdf01643c5c2
window.addEventListener('documentload', finish);
window.addEventListener('documentloaded', finish);
}
});
}
......@@ -61,7 +68,7 @@ class PDFMetadata {
return this._loaded.then(app => {
let uri = getPDFURL(app);
if (!uri) {
uri = fingerprintToURN(app.documentFingerprint);
uri = fingerprintToURN(app.pdfDocument.fingerprint);
}
return uri;
});
......@@ -86,7 +93,7 @@ class PDFMetadata {
}
const link = [
{href: fingerprintToURN(app.documentFingerprint)},
{href: fingerprintToURN(app.pdfDocument.fingerprint)},
];
const url = getPDFURL(app);
......@@ -97,7 +104,7 @@ class PDFMetadata {
return {
title: title,
link: link,
documentFingerprint: app.documentFingerprint,
documentFingerprint: app.pdfDocument.fingerprint,
};
});
}
......
......@@ -2,27 +2,107 @@
const PDFMetadata = require('../pdf-metadata');
describe('pdf-metadata', function () {
/**
* Fake implementation of PDF.js `window.PDFViewerApplication.metadata`.
*/
class FakeMetadata {
/**
* Initialize the metadata dictionary.
*
* @param {Object} metadata - A key/value dictionary of metadata fields.
*/
constructor(metadata) {
this._metadata = metadata;
}
get(key) {
return this._metadata[key];
}
has(key) {
return this._metadata.hasOwnProperty(key);
}
}
/**
* Fake implementation of PDF.js `window.PDFViewerApplication.pdfDocument`.
*/
class FakePDFDocumentProxy {
constructor({ fingerprint }) {
this.fingerprint = fingerprint;
}
}
/**
* Fake implementation of PDF.js `window.PDFViewerApplication` entry point.
*
* This fake only implements the parts that concern document metadata.
*/
class FakePDFViewerApplication {
/**
* Initialize the "PDF viewer" as it would be when loading a document or
* when a document fails to load.
*/
constructor(url = '') {
this.url = url;
this.documentInfo = undefined;
this.metadata = undefined;
this.pdfDocument = null;
}
/**
* Simulate completion of PDF document loading.
*/
finishLoading({ url, fingerprint, metadata, title, eventName = 'documentload' }) {
const event = document.createEvent('Event');
event.initEvent(eventName, false, false);
window.dispatchEvent(event);
this.url = url;
this.downloadComplete = true;
this.documentInfo = {};
if (typeof title !== undefined) {
this.documentInfo.Title = title;
}
if (metadata) {
this.metadata = new FakeMetadata(metadata);
}
this.pdfDocument = new FakePDFDocumentProxy({ fingerprint });
}
}
describe('annotator/plugin/pdf-metadata', function () {
[
// Event dispatched in older PDF.js versions (pre-7bc4bfcc).
'documentload',
// Event dispatched in newer PDF.js versions (post-7bc4bfcc).
'documentloaded',
].forEach(eventName => {
it('waits for the PDF to load before returning metadata', function () {
const fakeApp = {};
const fakeApp = new FakePDFViewerApplication;
const pdfMetadata = new PDFMetadata(fakeApp);
const event = document.createEvent('Event');
event.initEvent('documentload', false, false);
fakeApp.url = 'http://fake.com';
fakeApp.documentFingerprint = 'fakeFingerprint';
window.dispatchEvent(event);
fakeApp.finishLoading({
eventName,
url: 'http://fake.com',
fingerprint: 'fakeFingerprint',
});
return pdfMetadata.getUri().then(function (uri) {
assert.equal(uri, 'http://fake.com/');
});
});
});
it('does not wait for the PDF to load if it has already loaded', function () {
const fakePDFViewerApplication = {
const fakePDFViewerApplication = new FakePDFViewerApplication;
fakePDFViewerApplication.finishLoading({
url: 'http://fake.com',
documentFingerprint: 'fakeFingerprint',
};
fingerprint: 'fakeFingerprint',
});
const pdfMetadata = new PDFMetadata(fakePDFViewerApplication);
return pdfMetadata.getUri().then(function (uri) {
assert.equal(uri, 'http://fake.com/');
......@@ -31,18 +111,15 @@ describe('pdf-metadata', function () {
describe('metadata sources', function () {
let pdfMetadata;
const fakePDFViewerApplication = {
documentFingerprint: 'fakeFingerprint',
documentInfo: {
Title: 'fakeTitle',
},
const fakePDFViewerApplication = new FakePDFViewerApplication;
fakePDFViewerApplication.finishLoading({
fingerprint: 'fakeFingerprint',
title: 'fakeTitle',
metadata: {
metadata: {
'dc:title': 'fakeTitle',
},
'dc:title': 'dcFakeTitle',
},
url: 'http://fake.com/',
};
});
beforeEach(function () {
pdfMetadata = new PDFMetadata(fakePDFViewerApplication);
......@@ -56,10 +133,11 @@ describe('pdf-metadata', function () {
});
it('returns the fingerprint as a URN when the PDF URL is a local file', function () {
const fakePDFViewerApplication = {
const fakePDFViewerApplication = new FakePDFViewerApplication;
fakePDFViewerApplication.finishLoading({
url: 'file:///test.pdf',
documentFingerprint: 'fakeFingerprint',
};
fingerprint: 'fakeFingerprint',
});
const pdfMetadata = new PDFMetadata(fakePDFViewerApplication);
return pdfMetadata.getUri().then(function (uri) {
......@@ -68,10 +146,11 @@ describe('pdf-metadata', function () {
});
it('resolves relative URLs', () => {
const fakePDFViewerApplication = {
const fakePDFViewerApplication = new FakePDFViewerApplication;
fakePDFViewerApplication.finishLoading({
url: 'index.php?action=download&file_id=wibble',
documentFingerprint: 'fakeFingerprint',
};
fingerprint: 'fakeFingerprint',
});
const pdfMetadata = new PDFMetadata(fakePDFViewerApplication);
return pdfMetadata.getUri().then(uri => {
......@@ -85,15 +164,12 @@ describe('pdf-metadata', function () {
describe('#getMetadata', function () {
it('gets the title from the dc:title field', function () {
const expectedMetadata = {
title: 'dcTitle',
link: [{href: 'urn:x-pdf:' + fakePDFViewerApplication.documentFingerprint},
title: 'dcFakeTitle',
link: [{href: 'urn:x-pdf:' + fakePDFViewerApplication.pdfDocument.fingerprint},
{href: fakePDFViewerApplication.url}],
documentFingerprint: fakePDFViewerApplication.documentFingerprint,
documentFingerprint: fakePDFViewerApplication.pdfDocument.fingerprint,
};
fakePDFViewerApplication.metadata.has = sinon.stub().returns(true);
fakePDFViewerApplication.metadata.get = sinon.stub().returns('dcTitle');
return pdfMetadata.getMetadata().then(function (actualMetadata) {
assert.deepEqual(actualMetadata, expectedMetadata);
});
......@@ -102,9 +178,9 @@ describe('pdf-metadata', function () {
it('gets the title from the documentInfo.Title field', function () {
const expectedMetadata = {
title: fakePDFViewerApplication.documentInfo.Title,
link: [{href: 'urn:x-pdf:' + fakePDFViewerApplication.documentFingerprint},
link: [{href: 'urn:x-pdf:' + fakePDFViewerApplication.pdfDocument.fingerprint},
{href: fakePDFViewerApplication.url}],
documentFingerprint: fakePDFViewerApplication.documentFingerprint,
documentFingerprint: fakePDFViewerApplication.pdfDocument.fingerprint,
};
fakePDFViewerApplication.metadata.has = sinon.stub().returns(false);
......@@ -116,12 +192,13 @@ describe('pdf-metadata', function () {
it('does not save file:// URLs in document metadata', function () {
let pdfMetadata;
const fakePDFViewerApplication = {
documentFingerprint: 'fakeFingerprint',
const fakePDFViewerApplication = new FakePDFViewerApplication;
fakePDFViewerApplication.finishLoading({
fingerprint: 'fakeFingerprint',
url: 'file://fake.pdf',
};
});
const expectedMetadata = {
link: [{href: 'urn:x-pdf:' + fakePDFViewerApplication.documentFingerprint}],
link: [{href: 'urn:x-pdf:' + fakePDFViewerApplication.pdfDocument.fingerprint}],
};
pdfMetadata = new PDFMetadata(fakePDFViewerApplication);
......
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