Merge pull request #6988 from timvandermeij/fileattachment-annotation

Implement support for FileAttachment annotations
This commit is contained in:
Jonas Jenwald 2016-02-24 12:58:06 +01:00
commit 41efb92d3a
19 changed files with 222 additions and 34 deletions

View File

@ -88,7 +88,7 @@ var PDFViewerApplication = {
setTitleUsingUrl: function pdfViewSetTitleUsingUrl(url) { setTitleUsingUrl: function pdfViewSetTitleUsingUrl(url) {
this.url = url; this.url = url;
var title = PDFJS.getFileName(url) || url; var title = PDFJS.getFilenameFromUrl(url) || url;
try { try {
title = decodeURIComponent(title); title = decodeURIComponent(title);
} catch (e) { } catch (e) {

View File

@ -343,7 +343,7 @@ ChromeActions.prototype = {
try { try {
// contentDisposition/contentDispositionFilename is readonly before FF18 // contentDisposition/contentDispositionFilename is readonly before FF18
channel.contentDisposition = Ci.nsIChannel.DISPOSITION_ATTACHMENT; channel.contentDisposition = Ci.nsIChannel.DISPOSITION_ATTACHMENT;
if (self.contentDispositionFilename) { if (self.contentDispositionFilename && !data.isAttachment) {
channel.contentDispositionFilename = self.contentDispositionFilename; channel.contentDispositionFilename = self.contentDispositionFilename;
} else { } else {
channel.contentDispositionFilename = filename; channel.contentDispositionFilename = filename;

View File

@ -50,6 +50,7 @@ var isName = corePrimitives.isName;
var Stream = coreStream.Stream; var Stream = coreStream.Stream;
var ColorSpace = coreColorSpace.ColorSpace; var ColorSpace = coreColorSpace.ColorSpace;
var ObjectLoader = coreObj.ObjectLoader; var ObjectLoader = coreObj.ObjectLoader;
var FileSpec = coreObj.FileSpec;
var OperatorList = coreEvaluator.OperatorList; var OperatorList = coreEvaluator.OperatorList;
/** /**
@ -75,6 +76,7 @@ AnnotationFactory.prototype = /** @lends AnnotationFactory.prototype */ {
// Return the right annotation object based on the subtype and field type. // Return the right annotation object based on the subtype and field type.
var parameters = { var parameters = {
xref: xref,
dict: dict, dict: dict,
ref: ref ref: ref
}; };
@ -108,6 +110,9 @@ AnnotationFactory.prototype = /** @lends AnnotationFactory.prototype */ {
case 'StrikeOut': case 'StrikeOut':
return new StrikeOutAnnotation(parameters); return new StrikeOutAnnotation(parameters);
case 'FileAttachment':
return new FileAttachmentAnnotation(parameters);
default: default:
warn('Unimplemented annotation type "' + subtype + '", ' + warn('Unimplemented annotation type "' + subtype + '", ' +
'falling back to base annotation'); 'falling back to base annotation');
@ -852,6 +857,31 @@ var StrikeOutAnnotation = (function StrikeOutAnnotationClosure() {
return StrikeOutAnnotation; return StrikeOutAnnotation;
})(); })();
var FileAttachmentAnnotation = (function FileAttachmentAnnotationClosure() {
function FileAttachmentAnnotation(parameters) {
Annotation.call(this, parameters);
var dict = parameters.dict;
var file = new FileSpec(dict.get('FS'), parameters.xref);
this.data.annotationType = AnnotationType.FILEATTACHMENT;
this.data.file = file.serializable;
if (!dict.has('C')) {
// Fall back to the default background color.
this.data.color = null;
}
this.data.hasPopup = dict.has('Popup');
this.data.title = stringToPDFString(dict.get('T') || '');
this.data.contents = stringToPDFString(dict.get('Contents') || '');
}
Util.inherit(FileAttachmentAnnotation, Annotation, {});
return FileAttachmentAnnotation;
})();
exports.Annotation = Annotation; exports.Annotation = Annotation;
exports.AnnotationBorderStyle = AnnotationBorderStyle; exports.AnnotationBorderStyle = AnnotationBorderStyle;
exports.AnnotationFactory = AnnotationFactory; exports.AnnotationFactory = AnnotationFactory;

View File

@ -1601,4 +1601,5 @@ var ObjectLoader = (function() {
exports.Catalog = Catalog; exports.Catalog = Catalog;
exports.ObjectLoader = ObjectLoader; exports.ObjectLoader = ObjectLoader;
exports.XRef = XRef; exports.XRef = XRef;
exports.FileSpec = FileSpec;
})); }));

View File

@ -32,6 +32,7 @@ var AnnotationBorderStyleType = sharedUtil.AnnotationBorderStyleType;
var AnnotationType = sharedUtil.AnnotationType; var AnnotationType = sharedUtil.AnnotationType;
var Util = sharedUtil.Util; var Util = sharedUtil.Util;
var addLinkAttributes = sharedUtil.addLinkAttributes; var addLinkAttributes = sharedUtil.addLinkAttributes;
var getFilenameFromUrl = sharedUtil.getFilenameFromUrl;
var warn = sharedUtil.warn; var warn = sharedUtil.warn;
var CustomStyle = displayDOMUtils.CustomStyle; var CustomStyle = displayDOMUtils.CustomStyle;
@ -42,6 +43,7 @@ var CustomStyle = displayDOMUtils.CustomStyle;
* @property {PDFPage} page * @property {PDFPage} page
* @property {PageViewport} viewport * @property {PageViewport} viewport
* @property {IPDFLinkService} linkService * @property {IPDFLinkService} linkService
* @property {DownloadManager} downloadManager
*/ */
/** /**
@ -83,6 +85,9 @@ AnnotationElementFactory.prototype =
case AnnotationType.STRIKEOUT: case AnnotationType.STRIKEOUT:
return new StrikeOutAnnotationElement(parameters); return new StrikeOutAnnotationElement(parameters);
case AnnotationType.FILEATTACHMENT:
return new FileAttachmentAnnotationElement(parameters);
default: default:
return new AnnotationElement(parameters); return new AnnotationElement(parameters);
} }
@ -101,6 +106,7 @@ var AnnotationElement = (function AnnotationElementClosure() {
this.page = parameters.page; this.page = parameters.page;
this.viewport = parameters.viewport; this.viewport = parameters.viewport;
this.linkService = parameters.linkService; this.linkService = parameters.linkService;
this.downloadManager = parameters.downloadManager;
if (isRenderable) { if (isRenderable) {
this.container = this._createContainer(); this.container = this._createContainer();
@ -721,6 +727,76 @@ var StrikeOutAnnotationElement = (
return StrikeOutAnnotationElement; return StrikeOutAnnotationElement;
})(); })();
/**
* @class
* @alias FileAttachmentAnnotationElement
*/
var FileAttachmentAnnotationElement = (
function FileAttachmentAnnotationElementClosure() {
function FileAttachmentAnnotationElement(parameters) {
AnnotationElement.call(this, parameters, true);
this.filename = getFilenameFromUrl(parameters.data.file.filename);
this.content = parameters.data.file.content;
}
Util.inherit(FileAttachmentAnnotationElement, AnnotationElement, {
/**
* Render the file attachment annotation's HTML element in the empty
* container.
*
* @public
* @memberof FileAttachmentAnnotationElement
* @returns {HTMLSectionElement}
*/
render: function FileAttachmentAnnotationElement_render() {
this.container.className = 'fileAttachmentAnnotation';
var trigger = document.createElement('div');
trigger.style.height = this.container.style.height;
trigger.style.width = this.container.style.width;
trigger.addEventListener('dblclick', this._download.bind(this));
if (!this.data.hasPopup && (this.data.title || this.data.contents)) {
var popupElement = new PopupElement({
container: this.container,
trigger: trigger,
color: this.data.color,
title: this.data.title,
contents: this.data.contents,
hideWrapper: true
});
var popup = popupElement.render();
// Position the popup next to the FileAttachment annotation's
// container.
popup.style.left = this.container.style.width;
this.container.appendChild(popup);
}
this.container.appendChild(trigger);
return this.container;
},
/**
* Download the file attachment associated with this annotation.
*
* @private
* @memberof FileAttachmentAnnotationElement
*/
_download: function FileAttachmentAnnotationElement_download() {
if (!this.downloadManager) {
warn('Download cannot be started due to unavailable download manager');
return;
}
this.downloadManager.downloadData(this.content, this.filename, '');
}
});
return FileAttachmentAnnotationElement;
})();
/** /**
* @typedef {Object} AnnotationLayerParameters * @typedef {Object} AnnotationLayerParameters
* @property {PageViewport} viewport * @property {PageViewport} viewport
@ -757,7 +833,8 @@ var AnnotationLayer = (function AnnotationLayerClosure() {
layer: parameters.div, layer: parameters.div,
page: parameters.page, page: parameters.page,
viewport: parameters.viewport, viewport: parameters.viewport,
linkService: parameters.linkService linkService: parameters.linkService,
downloadManager: parameters.downloadManager
}; };
var element = annotationElementFactory.create(properties); var element = annotationElementFactory.create(properties);
if (element.isRenderable) { if (element.isRenderable) {

View File

@ -284,6 +284,17 @@ var UNSUPPORTED_FEATURES = PDFJS.UNSUPPORTED_FEATURES = {
font: 'font' font: 'font'
}; };
// Gets the file name from a given URL.
function getFilenameFromUrl(url) {
var anchor = url.indexOf('#');
var query = url.indexOf('?');
var end = Math.min(
anchor > 0 ? anchor : url.length,
query > 0 ? query : url.length);
return url.substring(url.lastIndexOf('/', end) + 1, end);
}
PDFJS.getFilenameFromUrl = getFilenameFromUrl;
// Combines two URLs. The baseUrl shall be absolute URL. If the url is an // Combines two URLs. The baseUrl shall be absolute URL. If the url is an
// absolute URL, it will be returned as is. // absolute URL, it will be returned as is.
function combineUrl(baseUrl, url) { function combineUrl(baseUrl, url) {
@ -2367,6 +2378,7 @@ exports.combineUrl = combineUrl;
exports.createPromiseCapability = createPromiseCapability; exports.createPromiseCapability = createPromiseCapability;
exports.deprecated = deprecated; exports.deprecated = deprecated;
exports.error = error; exports.error = error;
exports.getFilenameFromUrl = getFilenameFromUrl;
exports.getLookupTableFactory = getLookupTableFactory; exports.getLookupTableFactory = getLookupTableFactory;
exports.info = info; exports.info = info;
exports.isArray = isArray; exports.isArray = isArray;

View File

@ -217,3 +217,4 @@
!annotation-strikeout.pdf !annotation-strikeout.pdf
!annotation-squiggly.pdf !annotation-squiggly.pdf
!annotation-highlight.pdf !annotation-highlight.pdf
!annotation-fileattachment.pdf

Binary file not shown.

View File

@ -2773,6 +2773,13 @@
"type": "eq", "type": "eq",
"annotations": true "annotations": true
}, },
{ "id": "annotation-fileattachment",
"file": "pdfs/annotation-fileattachment.pdf",
"md5": "d20ecee4b53c81b2dd44c8715a1b4a83",
"rounds": 1,
"type": "eq",
"annotations": true
},
{ "id": "issue6108", { "id": "issue6108",
"file": "pdfs/issue6108.pdf", "file": "pdfs/issue6108.pdf",
"md5": "8961cb55149495989a80bf0487e0f076", "md5": "8961cb55149495989a80bf0487e0f076",

View File

@ -1,9 +1,25 @@
/* globals expect, it, describe, Dict, Name, Annotation, AnnotationBorderStyle, /* globals expect, it, describe, Dict, Name, Annotation, AnnotationBorderStyle,
AnnotationBorderStyleType, AnnotationFlag */ AnnotationBorderStyleType, AnnotationFlag, PDFJS, combineUrl,
waitsFor, beforeEach, afterEach, stringToBytes */
'use strict'; 'use strict';
describe('Annotation layer', function() { describe('Annotation layer', function() {
function waitsForPromiseResolved(promise, successCallback) {
var resolved = false;
promise.then(function(val) {
resolved = true;
successCallback(val);
},
function(error) {
// Shouldn't get here.
expect(error).toEqual('the promise should not have been rejected');
});
waitsFor(function() {
return resolved;
}, 20000);
}
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();
@ -172,4 +188,33 @@ describe('Annotation layer', function() {
expect(borderStyle.verticalCornerRadius).toEqual(0); expect(borderStyle.verticalCornerRadius).toEqual(0);
}); });
}); });
describe('FileAttachmentAnnotation', function() {
var loadingTask;
var annotations;
beforeEach(function() {
var pdfUrl = combineUrl(window.location.href,
'../pdfs/annotation-fileattachment.pdf');
loadingTask = PDFJS.getDocument(pdfUrl);
waitsForPromiseResolved(loadingTask.promise, function(pdfDocument) {
waitsForPromiseResolved(pdfDocument.getPage(1), function(pdfPage) {
waitsForPromiseResolved(pdfPage.getAnnotations(),
function (pdfAnnotations) {
annotations = pdfAnnotations;
});
});
});
});
afterEach(function() {
loadingTask.destroy();
});
it('should correctly parse a file attachment', function() {
var annotation = annotations[0];
expect(annotation.file.filename).toEqual('Test.txt');
expect(annotation.file.content).toEqual(stringToBytes('Test attachment'));
});
});
}); });

View File

@ -1,10 +1,25 @@
/* globals expect, it, describe, combineUrl, Dict, isDict, Name, PDFJS, /* globals expect, it, describe, combineUrl, Dict, isDict, Name, PDFJS,
stringToPDFString, isExternalLinkTargetSet, LinkTarget, stringToPDFString, isExternalLinkTargetSet, LinkTarget,
removeNullCharacters */ removeNullCharacters, getFilenameFromUrl */
'use strict'; 'use strict';
describe('util', function() { describe('util', function() {
describe('getFilenameFromUrl', function() {
it('should get the filename from an absolute URL', function() {
var url = 'http://server.org/filename.pdf';
var result = getFilenameFromUrl(url);
var expected = 'filename.pdf';
expect(result).toEqual(expected);
});
it('should get the filename from a relative URL', function() {
var url = '../../filename.pdf';
var result = getFilenameFromUrl(url);
var expected = 'filename.pdf';
expect(result).toEqual(expected);
});
});
describe('combineUrl', function() { describe('combineUrl', function() {
it('absolute url with protocol stays as is', function() { it('absolute url with protocol stays as is', function() {

View File

@ -72,6 +72,7 @@
.annotationLayer .highlightAnnotation, .annotationLayer .highlightAnnotation,
.annotationLayer .underlineAnnotation, .annotationLayer .underlineAnnotation,
.annotationLayer .squigglyAnnotation, .annotationLayer .squigglyAnnotation,
.annotationLayer .strikeoutAnnotation { .annotationLayer .strikeoutAnnotation,
.annotationLayer .fileAttachmentAnnotation {
cursor: pointer; cursor: pointer;
} }

View File

@ -21,6 +21,7 @@
* @property {HTMLDivElement} pageDiv * @property {HTMLDivElement} pageDiv
* @property {PDFPage} pdfPage * @property {PDFPage} pdfPage
* @property {IPDFLinkService} linkService * @property {IPDFLinkService} linkService
* @property {DownloadManager} downloadManager
*/ */
/** /**
@ -35,6 +36,7 @@ var AnnotationLayerBuilder = (function AnnotationLayerBuilderClosure() {
this.pageDiv = options.pageDiv; this.pageDiv = options.pageDiv;
this.pdfPage = options.pdfPage; this.pdfPage = options.pdfPage;
this.linkService = options.linkService; this.linkService = options.linkService;
this.downloadManager = options.downloadManager;
this.div = null; this.div = null;
} }
@ -59,7 +61,8 @@ var AnnotationLayerBuilder = (function AnnotationLayerBuilderClosure() {
div: self.div, div: self.div,
annotations: annotations, annotations: annotations,
page: self.pdfPage, page: self.pdfPage,
linkService: self.linkService linkService: self.linkService,
downloadManager: self.downloadManager
}; };
if (self.div) { if (self.div) {

View File

@ -12,7 +12,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
/* globals getFileName, PDFJS */ /* globals PDFJS */
'use strict'; 'use strict';
@ -84,7 +84,7 @@ var PDFAttachmentView = (function PDFAttachmentViewClosure() {
for (var i = 0; i < attachmentsCount; i++) { for (var i = 0; i < attachmentsCount; i++) {
var item = attachments[names[i]]; var item = attachments[names[i]];
var filename = getFileName(item.filename); var filename = PDFJS.getFilenameFromUrl(item.filename);
var div = document.createElement('div'); var div = document.createElement('div');
div.className = 'attachmentsItem'; div.className = 'attachmentsItem';
var button = document.createElement('button'); var button = document.createElement('button');

View File

@ -13,8 +13,7 @@
* limitations under the License. * limitations under the License.
*/ */
/* globals RenderingStates, PDFJS, DEFAULT_SCALE, CSS_UNITS, getOutputScale, /* globals RenderingStates, PDFJS, DEFAULT_SCALE, CSS_UNITS, getOutputScale,
TextLayerBuilder, AnnotationLayerBuilder, Promise, TextLayerBuilder, Promise, approximateFraction, roundToDivide */
approximateFraction, roundToDivide */
'use strict'; 'use strict';

View File

@ -15,7 +15,7 @@
/*jshint globalstrict: false */ /*jshint globalstrict: false */
/* globals PDFJS, PDFViewer, PDFPageView, TextLayerBuilder, PDFLinkService, /* globals PDFJS, PDFViewer, PDFPageView, TextLayerBuilder, PDFLinkService,
DefaultTextLayerFactory, AnnotationLayerBuilder, PDFHistory, DefaultTextLayerFactory, AnnotationLayerBuilder, PDFHistory,
DefaultAnnotationLayerFactory, getFileName, ProgressBar */ DefaultAnnotationLayerFactory, DownloadManager, ProgressBar */
// Initializing PDFJS global object (if still undefined) // Initializing PDFJS global object (if still undefined)
if (typeof PDFJS === 'undefined') { if (typeof PDFJS === 'undefined') {
@ -29,6 +29,7 @@ if (typeof PDFJS === 'undefined') {
//#include pdf_link_service.js //#include pdf_link_service.js
//#include pdf_viewer.js //#include pdf_viewer.js
//#include pdf_history.js //#include pdf_history.js
//#include download_manager.js
PDFJS.PDFViewer = PDFViewer; PDFJS.PDFViewer = PDFViewer;
PDFJS.PDFPageView = PDFPageView; PDFJS.PDFPageView = PDFPageView;
@ -39,6 +40,6 @@ if (typeof PDFJS === 'undefined') {
PDFJS.DefaultAnnotationLayerFactory = DefaultAnnotationLayerFactory; PDFJS.DefaultAnnotationLayerFactory = DefaultAnnotationLayerFactory;
PDFJS.PDFHistory = PDFHistory; PDFJS.PDFHistory = PDFHistory;
PDFJS.getFileName = getFileName; PDFJS.DownloadManager = DownloadManager;
PDFJS.ProgressBar = ProgressBar; PDFJS.ProgressBar = ProgressBar;
}).call((typeof window === 'undefined') ? this : window); }).call((typeof window === 'undefined') ? this : window);

View File

@ -40,6 +40,8 @@ var DEFAULT_CACHE_SIZE = 10;
* @property {HTMLDivElement} container - The container for the viewer element. * @property {HTMLDivElement} container - The container for the viewer element.
* @property {HTMLDivElement} viewer - (optional) The viewer element. * @property {HTMLDivElement} viewer - (optional) The viewer element.
* @property {IPDFLinkService} linkService - The navigation/linking service. * @property {IPDFLinkService} linkService - The navigation/linking service.
* @property {DownloadManager} downloadManager - (optional) The download
* manager component.
* @property {PDFRenderingQueue} renderingQueue - (optional) The rendering * @property {PDFRenderingQueue} renderingQueue - (optional) The rendering
* queue object. * queue object.
* @property {boolean} removePageBorders - (optional) Removes the border shadow * @property {boolean} removePageBorders - (optional) Removes the border shadow
@ -92,6 +94,7 @@ var PDFViewer = (function pdfViewer() {
this.container = options.container; this.container = options.container;
this.viewer = options.viewer || options.container.firstElementChild; this.viewer = options.viewer || options.container.firstElementChild;
this.linkService = options.linkService || new SimpleLinkService(); this.linkService = options.linkService || new SimpleLinkService();
this.downloadManager = options.downloadManager || null;
this.removePageBorders = options.removePageBorders || false; this.removePageBorders = options.removePageBorders || false;
this.defaultRenderingQueue = !options.renderingQueue; this.defaultRenderingQueue = !options.renderingQueue;
@ -757,7 +760,8 @@ var PDFViewer = (function pdfViewer() {
return new AnnotationLayerBuilder({ return new AnnotationLayerBuilder({
pageDiv: pageDiv, pageDiv: pageDiv,
pdfPage: pdfPage, pdfPage: pdfPage,
linkService: this.linkService linkService: this.linkService,
downloadManager: this.downloadManager
}); });
}, },

View File

@ -23,15 +23,6 @@ var MAX_AUTO_SCALE = 1.25;
var SCROLLBAR_PADDING = 40; var SCROLLBAR_PADDING = 40;
var VERTICAL_PADDING = 5; var VERTICAL_PADDING = 5;
function getFileName(url) {
var anchor = url.indexOf('#');
var query = url.indexOf('?');
var end = Math.min(
anchor > 0 ? anchor : url.length,
query > 0 ? query : url.length);
return url.substring(url.lastIndexOf('/', end) + 1, end);
}
/** /**
* Returns scale factor for the canvas. It makes sense for the HiDPI displays. * Returns scale factor for the canvas. It makes sense for the HiDPI displays.
* @return {Object} The object with horizontal (sx) and vertical (sy) * @return {Object} The object with horizontal (sx) and vertical (sy)

View File

@ -12,15 +12,15 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
/* globals PDFJS, PDFBug, FirefoxCom, Stats, ProgressBar, /* globals PDFJS, PDFBug, FirefoxCom, Stats, ProgressBar, DownloadManager,
DownloadManager, getFileName, getPDFFileNameFromURL, getPDFFileNameFromURL, PDFHistory, Preferences, SidebarView,
PDFHistory, Preferences, SidebarView, ViewHistory, Stats, ViewHistory, Stats, PDFThumbnailViewer, URL, noContextMenuHandler,
PDFThumbnailViewer, URL, noContextMenuHandler, SecondaryToolbar, SecondaryToolbar, PasswordPrompt, PDFPresentationMode,
PasswordPrompt, PDFPresentationMode, PDFDocumentProperties, HandTool, PDFDocumentProperties, HandTool, Promise, PDFLinkService,
Promise, PDFLinkService, PDFOutlineView, PDFAttachmentView, PDFOutlineView, PDFAttachmentView, OverlayManager,
OverlayManager, PDFFindController, PDFFindBar, PDFViewer, PDFFindController, PDFFindBar, PDFViewer, PDFRenderingQueue,
PDFRenderingQueue, PresentationModeState, parseQueryString, PresentationModeState, parseQueryString, RenderingStates,
RenderingStates, UNKNOWN_SCALE, DEFAULT_SCALE_VALUE, UNKNOWN_SCALE, DEFAULT_SCALE_VALUE,
IGNORE_CURRENT_POSITION_ON_ZOOM: true */ IGNORE_CURRENT_POSITION_ON_ZOOM: true */
'use strict'; 'use strict';
@ -134,7 +134,8 @@ var PDFViewerApplication = {
container: container, container: container,
viewer: viewer, viewer: viewer,
renderingQueue: pdfRenderingQueue, renderingQueue: pdfRenderingQueue,
linkService: pdfLinkService linkService: pdfLinkService,
downloadManager: new DownloadManager()
}); });
pdfRenderingQueue.setViewer(this.pdfViewer); pdfRenderingQueue.setViewer(this.pdfViewer);
pdfLinkService.setViewer(this.pdfViewer); pdfLinkService.setViewer(this.pdfViewer);
@ -485,7 +486,7 @@ var PDFViewerApplication = {
setTitleUsingUrl: function pdfViewSetTitleUsingUrl(url) { setTitleUsingUrl: function pdfViewSetTitleUsingUrl(url) {
this.url = url; this.url = url;
try { try {
this.setTitle(decodeURIComponent(getFileName(url)) || url); this.setTitle(decodeURIComponent(PDFJS.getFilenameFromUrl(url)) || url);
} catch (e) { } catch (e) {
// decodeURIComponent may throw URIError, // decodeURIComponent may throw URIError,
// fall back to using the unprocessed url in that case // fall back to using the unprocessed url in that case