Merge pull request #7116 from Snuffleupagus/refactor-LinkAnnotation-tests
Refactor `LinkAnnotation` slightly, improve handling of the `GoToR` action, and add unit-tests
This commit is contained in:
commit
452c031af5
@ -37,6 +37,8 @@ var AnnotationFlag = sharedUtil.AnnotationFlag;
|
|||||||
var AnnotationType = sharedUtil.AnnotationType;
|
var AnnotationType = sharedUtil.AnnotationType;
|
||||||
var OPS = sharedUtil.OPS;
|
var OPS = sharedUtil.OPS;
|
||||||
var Util = sharedUtil.Util;
|
var Util = sharedUtil.Util;
|
||||||
|
var isBool = sharedUtil.isBool;
|
||||||
|
var isString = sharedUtil.isString;
|
||||||
var isArray = sharedUtil.isArray;
|
var isArray = sharedUtil.isArray;
|
||||||
var isInt = sharedUtil.isInt;
|
var isInt = sharedUtil.isInt;
|
||||||
var isValidUrl = sharedUtil.isValidUrl;
|
var isValidUrl = sharedUtil.isValidUrl;
|
||||||
@ -705,68 +707,93 @@ var LinkAnnotation = (function LinkAnnotationClosure() {
|
|||||||
var data = this.data;
|
var data = this.data;
|
||||||
data.annotationType = AnnotationType.LINK;
|
data.annotationType = AnnotationType.LINK;
|
||||||
|
|
||||||
var action = dict.get('A');
|
var action = dict.get('A'), url, dest;
|
||||||
if (action && isDict(action)) {
|
if (action && isDict(action)) {
|
||||||
var linkType = action.get('S').name;
|
var linkType = action.get('S').name;
|
||||||
if (linkType === 'URI') {
|
switch (linkType) {
|
||||||
var url = action.get('URI');
|
case 'URI':
|
||||||
if (isName(url)) {
|
url = action.get('URI');
|
||||||
// Some bad PDFs do not put parentheses around relative URLs.
|
if (isName(url)) {
|
||||||
url = '/' + url.name;
|
// Some bad PDFs do not put parentheses around relative URLs.
|
||||||
} else if (url) {
|
url = '/' + url.name;
|
||||||
url = addDefaultProtocolToUrl(url);
|
} else if (url) {
|
||||||
}
|
url = addDefaultProtocolToUrl(url);
|
||||||
// TODO: pdf spec mentions urls can be relative to a Base
|
}
|
||||||
// entry in the dictionary.
|
// TODO: pdf spec mentions urls can be relative to a Base
|
||||||
if (!isValidUrl(url, false)) {
|
// entry in the dictionary.
|
||||||
url = '';
|
break;
|
||||||
}
|
|
||||||
// According to ISO 32000-1:2008, section 12.6.4.7,
|
|
||||||
// URI should to be encoded in 7-bit ASCII.
|
|
||||||
// Some bad PDFs may have URIs in UTF-8 encoding, see Bugzilla 1122280.
|
|
||||||
try {
|
|
||||||
data.url = stringToUTF8String(url);
|
|
||||||
} catch (e) {
|
|
||||||
// Fall back to a simple copy.
|
|
||||||
data.url = url;
|
|
||||||
}
|
|
||||||
} else if (linkType === 'GoTo') {
|
|
||||||
data.dest = action.get('D');
|
|
||||||
} else if (linkType === 'GoToR') {
|
|
||||||
var urlDict = action.get('F');
|
|
||||||
if (isDict(urlDict)) {
|
|
||||||
// We assume that the 'url' is a Filspec dictionary
|
|
||||||
// and fetch the url without checking any further
|
|
||||||
url = urlDict.get('F') || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: pdf reference says that GoToR
|
case 'GoTo':
|
||||||
// can also have 'NewWindow' attribute
|
dest = action.get('D');
|
||||||
if (!isValidUrl(url, false)) {
|
break;
|
||||||
url = '';
|
|
||||||
}
|
case 'GoToR':
|
||||||
data.url = url;
|
var urlDict = action.get('F');
|
||||||
data.dest = action.get('D');
|
if (isDict(urlDict)) {
|
||||||
} else if (linkType === 'Named') {
|
// We assume that we found a FileSpec dictionary
|
||||||
data.action = action.get('N').name;
|
// and fetch the URL without checking any further.
|
||||||
} else {
|
url = urlDict.get('F') || null;
|
||||||
warn('unrecognized link type: ' + linkType);
|
} else if (isString(urlDict)) {
|
||||||
|
url = urlDict;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: the destination is relative to the *remote* document.
|
||||||
|
var remoteDest = action.get('D');
|
||||||
|
if (remoteDest) {
|
||||||
|
if (isName(remoteDest)) {
|
||||||
|
remoteDest = remoteDest.name;
|
||||||
|
}
|
||||||
|
if (isString(remoteDest) && isString(url)) {
|
||||||
|
var baseUrl = url.split('#')[0];
|
||||||
|
url = baseUrl + '#' + remoteDest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The 'NewWindow' property, equal to `LinkTarget.BLANK`.
|
||||||
|
var newWindow = action.get('NewWindow');
|
||||||
|
if (isBool(newWindow)) {
|
||||||
|
data.newWindow = newWindow;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Named':
|
||||||
|
data.action = action.get('N').name;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
warn('unrecognized link type: ' + linkType);
|
||||||
}
|
}
|
||||||
} else if (dict.has('Dest')) {
|
} else if (dict.has('Dest')) { // Simple destination link.
|
||||||
// simple destination link
|
dest = dict.get('Dest');
|
||||||
var dest = dict.get('Dest');
|
}
|
||||||
|
|
||||||
|
if (url) {
|
||||||
|
if (isValidUrl(url, /* allowRelative = */ false)) {
|
||||||
|
data.url = tryConvertUrlEncoding(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dest) {
|
||||||
data.dest = isName(dest) ? dest.name : dest;
|
data.dest = isName(dest) ? dest.name : dest;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lets URLs beginning with 'www.' default to using the 'http://' protocol.
|
// Lets URLs beginning with 'www.' default to using the 'http://' protocol.
|
||||||
function addDefaultProtocolToUrl(url) {
|
function addDefaultProtocolToUrl(url) {
|
||||||
if (url && url.indexOf('www.') === 0) {
|
if (isString(url) && url.indexOf('www.') === 0) {
|
||||||
return ('http://' + url);
|
return ('http://' + url);
|
||||||
}
|
}
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function tryConvertUrlEncoding(url) {
|
||||||
|
// According to ISO 32000-1:2008, section 12.6.4.7, URIs should be encoded
|
||||||
|
// in 7-bit ASCII. Some bad PDFs use UTF-8 encoding, see Bugzilla 1122280.
|
||||||
|
try {
|
||||||
|
return stringToUTF8String(url);
|
||||||
|
} catch (e) {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Util.inherit(LinkAnnotation, Annotation, {});
|
Util.inherit(LinkAnnotation, Annotation, {});
|
||||||
|
|
||||||
return LinkAnnotation;
|
return LinkAnnotation;
|
||||||
|
@ -31,6 +31,7 @@ var AnnotationBorderStyleType = sharedUtil.AnnotationBorderStyleType;
|
|||||||
var AnnotationType = sharedUtil.AnnotationType;
|
var AnnotationType = sharedUtil.AnnotationType;
|
||||||
var Util = sharedUtil.Util;
|
var Util = sharedUtil.Util;
|
||||||
var addLinkAttributes = displayDOMUtils.addLinkAttributes;
|
var addLinkAttributes = displayDOMUtils.addLinkAttributes;
|
||||||
|
var LinkTarget = displayDOMUtils.LinkTarget;
|
||||||
var getFilenameFromUrl = displayDOMUtils.getFilenameFromUrl;
|
var getFilenameFromUrl = displayDOMUtils.getFilenameFromUrl;
|
||||||
var warn = sharedUtil.warn;
|
var warn = sharedUtil.warn;
|
||||||
var CustomStyle = displayDOMUtils.CustomStyle;
|
var CustomStyle = displayDOMUtils.CustomStyle;
|
||||||
@ -278,13 +279,16 @@ var LinkAnnotationElement = (function LinkAnnotationElementClosure() {
|
|||||||
this.container.className = 'linkAnnotation';
|
this.container.className = 'linkAnnotation';
|
||||||
|
|
||||||
var link = document.createElement('a');
|
var link = document.createElement('a');
|
||||||
addLinkAttributes(link, { url: this.data.url });
|
addLinkAttributes(link, {
|
||||||
|
url: this.data.url,
|
||||||
|
target: (this.data.newWindow ? LinkTarget.BLANK : undefined),
|
||||||
|
});
|
||||||
|
|
||||||
if (!this.data.url) {
|
if (!this.data.url) {
|
||||||
if (this.data.action) {
|
if (this.data.action) {
|
||||||
this._bindNamedAction(link, this.data.action);
|
this._bindNamedAction(link, this.data.action);
|
||||||
} else {
|
} else {
|
||||||
this._bindLink(link, ('dest' in this.data) ? this.data.dest : null);
|
this._bindLink(link, (this.data.dest || null));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,15 +114,15 @@ var LinkTargetStringMap = [
|
|||||||
/**
|
/**
|
||||||
* @typedef ExternalLinkParameters
|
* @typedef ExternalLinkParameters
|
||||||
* @typedef {Object} ExternalLinkParameters
|
* @typedef {Object} ExternalLinkParameters
|
||||||
* @property {string} url
|
* @property {string} url - An absolute URL.
|
||||||
* @property {LinkTarget} target
|
* @property {LinkTarget} target - The link target.
|
||||||
* @property {string} rel
|
* @property {string} rel - The link relationship.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds various attributes (href, title, target, rel) to hyperlinks.
|
* Adds various attributes (href, title, target, rel) to hyperlinks.
|
||||||
* @param {HTMLLinkElement} link - The link element.
|
* @param {HTMLLinkElement} link - The link element.
|
||||||
* @param {ExternalLinkParameters} params - An object with the properties.
|
* @param {ExternalLinkParameters} params
|
||||||
*/
|
*/
|
||||||
function addLinkAttributes(link, params) {
|
function addLinkAttributes(link, params) {
|
||||||
var url = params && params.url;
|
var url = params && params.url;
|
||||||
@ -134,7 +134,7 @@ function addLinkAttributes(link, params) {
|
|||||||
target = getDefaultSetting('externalLinkTarget');
|
target = getDefaultSetting('externalLinkTarget');
|
||||||
}
|
}
|
||||||
link.target = LinkTargetStringMap[target];
|
link.target = LinkTargetStringMap[target];
|
||||||
// Strip referrer from the URL.
|
|
||||||
var rel = params.rel;
|
var rel = params.rel;
|
||||||
if (typeof rel === 'undefined') {
|
if (typeof rel === 'undefined') {
|
||||||
rel = getDefaultSetting('externalLinkRel');
|
rel = getDefaultSetting('externalLinkRel');
|
||||||
|
@ -312,7 +312,7 @@ function isSameOrigin(baseUrl, otherUrl) {
|
|||||||
|
|
||||||
// Validates if URL is safe and allowed, e.g. to avoid XSS.
|
// Validates if URL is safe and allowed, e.g. to avoid XSS.
|
||||||
function isValidUrl(url, allowRelative) {
|
function isValidUrl(url, allowRelative) {
|
||||||
if (!url) {
|
if (!url || typeof url !== 'string') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// RFC 3986 (http://tools.ietf.org/html/rfc3986#section-3.1)
|
// RFC 3986 (http://tools.ietf.org/html/rfc3986#section-3.1)
|
||||||
|
@ -1,10 +1,20 @@
|
|||||||
/* globals expect, it, describe, Dict, Name, Annotation, AnnotationBorderStyle,
|
/* globals expect, it, describe, Dict, Name, Annotation, AnnotationBorderStyle,
|
||||||
AnnotationBorderStyleType, AnnotationFlag, PDFJS,
|
AnnotationBorderStyleType, AnnotationType, AnnotationFlag, PDFJS,
|
||||||
beforeEach, afterEach, stringToBytes */
|
beforeEach, afterEach, stringToBytes, AnnotationFactory, Ref,
|
||||||
|
beforeAll, afterAll */
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
describe('Annotation layer', function() {
|
describe('Annotation layer', function() {
|
||||||
|
function XrefMock(queue) {
|
||||||
|
this.queue = queue || [];
|
||||||
|
}
|
||||||
|
XrefMock.prototype = {
|
||||||
|
fetchIfRef: function() {
|
||||||
|
return this.queue.shift();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
describe('Annotation', function() {
|
describe('Annotation', function() {
|
||||||
it('should set and get flags', function() {
|
it('should set and get flags', function() {
|
||||||
var dict = new Dict();
|
var dict = new Dict();
|
||||||
@ -174,12 +184,156 @@ describe('Annotation layer', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('LinkAnnotation', function() {
|
||||||
|
var annotationFactory;
|
||||||
|
|
||||||
|
beforeAll(function (done) {
|
||||||
|
annotationFactory = new AnnotationFactory();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(function () {
|
||||||
|
annotationFactory = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should correctly parse a URI action', function() {
|
||||||
|
var actionDict = new Dict();
|
||||||
|
actionDict.set('Type', Name.get('Action'));
|
||||||
|
actionDict.set('S', Name.get('URI'));
|
||||||
|
actionDict.set('URI', 'http://www.ctan.org/tex-archive/info/lshort');
|
||||||
|
|
||||||
|
var annotationDict = new Dict();
|
||||||
|
annotationDict.set('Type', Name.get('Annot'));
|
||||||
|
annotationDict.set('Subtype', Name.get('Link'));
|
||||||
|
annotationDict.set('A', actionDict);
|
||||||
|
|
||||||
|
var xrefMock = new XrefMock([annotationDict]);
|
||||||
|
var annotationRef = new Ref(820, 0);
|
||||||
|
|
||||||
|
var annotation = annotationFactory.create(xrefMock, annotationRef);
|
||||||
|
var data = annotation.data;
|
||||||
|
expect(data.annotationType).toEqual(AnnotationType.LINK);
|
||||||
|
|
||||||
|
expect(data.url).toEqual('http://www.ctan.org/tex-archive/info/lshort');
|
||||||
|
expect(data.dest).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should correctly parse a URI action, where the URI entry ' +
|
||||||
|
'is missing a protocol', function() {
|
||||||
|
var actionDict = new Dict();
|
||||||
|
actionDict.set('Type', Name.get('Action'));
|
||||||
|
actionDict.set('S', Name.get('URI'));
|
||||||
|
actionDict.set('URI', 'www.hmrc.gov.uk');
|
||||||
|
|
||||||
|
var annotationDict = new Dict();
|
||||||
|
annotationDict.set('Type', Name.get('Annot'));
|
||||||
|
annotationDict.set('Subtype', Name.get('Link'));
|
||||||
|
annotationDict.set('A', actionDict);
|
||||||
|
|
||||||
|
var xrefMock = new XrefMock([annotationDict]);
|
||||||
|
var annotationRef = new Ref(353, 0);
|
||||||
|
|
||||||
|
var annotation = annotationFactory.create(xrefMock, annotationRef);
|
||||||
|
var data = annotation.data;
|
||||||
|
expect(data.annotationType).toEqual(AnnotationType.LINK);
|
||||||
|
|
||||||
|
expect(data.url).toEqual('http://www.hmrc.gov.uk');
|
||||||
|
expect(data.dest).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should correctly parse a GoTo action', function() {
|
||||||
|
var actionDict = new Dict();
|
||||||
|
actionDict.set('Type', Name.get('Action'));
|
||||||
|
actionDict.set('S', Name.get('GoTo'));
|
||||||
|
actionDict.set('D', 'page.157');
|
||||||
|
|
||||||
|
var annotationDict = new Dict();
|
||||||
|
annotationDict.set('Type', Name.get('Annot'));
|
||||||
|
annotationDict.set('Subtype', Name.get('Link'));
|
||||||
|
annotationDict.set('A', actionDict);
|
||||||
|
|
||||||
|
var xrefMock = new XrefMock([annotationDict]);
|
||||||
|
var annotationRef = new Ref(798, 0);
|
||||||
|
|
||||||
|
var annotation = annotationFactory.create(xrefMock, annotationRef);
|
||||||
|
var data = annotation.data;
|
||||||
|
expect(data.annotationType).toEqual(AnnotationType.LINK);
|
||||||
|
|
||||||
|
expect(data.url).toBeUndefined();
|
||||||
|
expect(data.dest).toEqual('page.157');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should correctly parse a GoToR action, where the FileSpec entry ' +
|
||||||
|
'is a string containing a relative URL', function() {
|
||||||
|
var actionDict = new Dict();
|
||||||
|
actionDict.set('Type', Name.get('Action'));
|
||||||
|
actionDict.set('S', Name.get('GoToR'));
|
||||||
|
actionDict.set('F', '../../0021/002156/215675E.pdf');
|
||||||
|
actionDict.set('D', '15');
|
||||||
|
|
||||||
|
var annotationDict = new Dict();
|
||||||
|
annotationDict.set('Type', Name.get('Annot'));
|
||||||
|
annotationDict.set('Subtype', Name.get('Link'));
|
||||||
|
annotationDict.set('A', actionDict);
|
||||||
|
|
||||||
|
var xrefMock = new XrefMock([annotationDict]);
|
||||||
|
var annotationRef = new Ref(489, 0);
|
||||||
|
|
||||||
|
var annotation = annotationFactory.create(xrefMock, annotationRef);
|
||||||
|
var data = annotation.data;
|
||||||
|
expect(data.annotationType).toEqual(AnnotationType.LINK);
|
||||||
|
|
||||||
|
expect(data.url).toBeUndefined();
|
||||||
|
expect(data.dest).toBeUndefined();
|
||||||
|
expect(data.newWindow).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should correctly parse a Named action', function() {
|
||||||
|
var actionDict = new Dict();
|
||||||
|
actionDict.set('Type', Name.get('Action'));
|
||||||
|
actionDict.set('S', Name.get('Named'));
|
||||||
|
actionDict.set('N', Name.get('GoToPage'));
|
||||||
|
|
||||||
|
var annotationDict = new Dict();
|
||||||
|
annotationDict.set('Type', Name.get('Annot'));
|
||||||
|
annotationDict.set('Subtype', Name.get('Link'));
|
||||||
|
annotationDict.set('A', actionDict);
|
||||||
|
|
||||||
|
var xrefMock = new XrefMock([annotationDict]);
|
||||||
|
var annotationRef = new Ref(12, 0);
|
||||||
|
|
||||||
|
var annotation = annotationFactory.create(xrefMock, annotationRef);
|
||||||
|
var data = annotation.data;
|
||||||
|
expect(data.annotationType).toEqual(AnnotationType.LINK);
|
||||||
|
|
||||||
|
expect(data.url).toBeUndefined();
|
||||||
|
expect(data.action).toEqual('GoToPage');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should correctly parse a simple Dest', function() {
|
||||||
|
var annotationDict = new Dict();
|
||||||
|
annotationDict.set('Type', Name.get('Annot'));
|
||||||
|
annotationDict.set('Subtype', Name.get('Link'));
|
||||||
|
annotationDict.set('Dest', Name.get('LI0'));
|
||||||
|
|
||||||
|
var xrefMock = new XrefMock([annotationDict]);
|
||||||
|
var annotationRef = new Ref(583, 0);
|
||||||
|
|
||||||
|
var annotation = annotationFactory.create(xrefMock, annotationRef);
|
||||||
|
var data = annotation.data;
|
||||||
|
expect(data.annotationType).toEqual(AnnotationType.LINK);
|
||||||
|
|
||||||
|
expect(data.url).toBeUndefined();
|
||||||
|
expect(data.dest).toEqual('LI0');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('FileAttachmentAnnotation', function() {
|
describe('FileAttachmentAnnotation', function() {
|
||||||
var loadingTask;
|
var loadingTask;
|
||||||
var annotations;
|
var annotations;
|
||||||
|
|
||||||
beforeEach(function(done) {
|
beforeEach(function(done) {
|
||||||
var pdfUrl = new URL('../pdfs/annotation-fileattachment.pdf',
|
var pdfUrl = new URL('../pdfs/annotation-fileattachment.pdf',
|
||||||
window.location).href;
|
window.location).href;
|
||||||
loadingTask = PDFJS.getDocument(pdfUrl);
|
loadingTask = PDFJS.getDocument(pdfUrl);
|
||||||
loadingTask.promise.then(function(pdfDocument) {
|
loadingTask.promise.then(function(pdfDocument) {
|
||||||
|
Loading…
Reference in New Issue
Block a user