From 8ba80729373629420d17fcf7b25156824b659502 Mon Sep 17 00:00:00 2001
From: Jonas Jenwald <jonas.jenwald@gmail.com>
Date: Fri, 14 Jul 2017 16:30:25 +0200
Subject: [PATCH] Convert `PDFLinkService` to an ES6 class

---
 web/interfaces.js       |   5 +
 web/pdf_link_service.js | 844 ++++++++++++++++++++--------------------
 2 files changed, 421 insertions(+), 428 deletions(-)

diff --git a/web/interfaces.js b/web/interfaces.js
index 87aff84f9..69cd55d5f 100644
--- a/web/interfaces.js
+++ b/web/interfaces.js
@@ -57,6 +57,11 @@ class IPDFLinkService {
    */
   executeNamedAction(action) {}
 
+  /**
+   * @param {Object} params
+   */
+  onFileAttachmentAnnotation({ id, filename, content, }) {}
+
   /**
    * @param {number} pageNum - page number.
    * @param {Object} pageRef - reference to the page.
diff --git a/web/pdf_link_service.js b/web/pdf_link_service.js
index 984a75314..cb592cc9b 100644
--- a/web/pdf_link_service.js
+++ b/web/pdf_link_service.js
@@ -24,17 +24,14 @@ import { parseQueryString } from './ui_utils';
 /**
  * Performs navigation functions inside PDF, such as opening specified page,
  * or destination.
- * @class
  * @implements {IPDFLinkService}
  */
-var PDFLinkService = (function PDFLinkServiceClosure() {
+class PDFLinkService {
   /**
-   * @constructs PDFLinkService
    * @param {PDFLinkServiceOptions} options
    */
-  function PDFLinkService(options) {
-    options = options || {};
-    this.eventBus = options.eventBus || getGlobalEventBus();
+  constructor({ eventBus, } = {}) {
+    this.eventBus = eventBus || getGlobalEventBus();
     this.baseUrl = null;
     this.pdfDocument = null;
     this.pdfViewer = null;
@@ -43,432 +40,423 @@ var PDFLinkService = (function PDFLinkServiceClosure() {
     this._pagesRefCache = null;
   }
 
-  PDFLinkService.prototype = {
-    setDocument: function PDFLinkService_setDocument(pdfDocument, baseUrl) {
-      this.baseUrl = baseUrl;
-      this.pdfDocument = pdfDocument;
-      this._pagesRefCache = Object.create(null);
-    },
-
-    setViewer: function PDFLinkService_setViewer(pdfViewer) {
-      this.pdfViewer = pdfViewer;
-    },
-
-    setHistory: function PDFLinkService_setHistory(pdfHistory) {
-      this.pdfHistory = pdfHistory;
-    },
-
-    /**
-     * @returns {number}
-     */
-    get pagesCount() {
-      return this.pdfDocument ? this.pdfDocument.numPages : 0;
-    },
-
-    /**
-     * @returns {number}
-     */
-    get page() {
-      return this.pdfViewer.currentPageNumber;
-    },
-
-    /**
-     * @param {number} value
-     */
-    set page(value) {
-      this.pdfViewer.currentPageNumber = value;
-    },
-
-    /**
-     * @param {string|Array} dest - The named, or explicit, PDF destination.
-     */
-    navigateTo(dest) {
-      let goToDestination = ({ namedDest, explicitDest, }) => {
-        // Dest array looks like that: <page-ref> </XYZ|/FitXXX> <args..>
-        let destRef = explicitDest[0], pageNumber;
-
-        if (destRef instanceof Object) {
-          pageNumber = this._cachedPageNumber(destRef);
-
-          if (pageNumber === null) {
-            // Fetch the page reference if it's not yet available. This could
-            // only occur during loading, before all pages have been resolved.
-            this.pdfDocument.getPageIndex(destRef).then((pageIndex) => {
-              this.cachePageRef(pageIndex + 1, destRef);
-              goToDestination({ namedDest, explicitDest, });
-            }).catch(() => {
-              console.error(`PDFLinkService.navigateTo: "${destRef}" is not ` +
-                            `a valid page reference, for dest="${dest}".`);
-            });
-            return;
-          }
-        } else if ((destRef | 0) === destRef) { // Integer
-          pageNumber = destRef + 1;
-        } else {
-          console.error(`PDFLinkService.navigateTo: "${destRef}" is not ` +
-                        `a valid destination reference, for dest="${dest}".`);
-          return;
-        }
-        if (!pageNumber || pageNumber < 1 || pageNumber > this.pagesCount) {
-          console.error(`PDFLinkService.navigateTo: "${pageNumber}" is not ` +
-                        `a valid page number, for dest="${dest}".`);
-          return;
-        }
-
-        this.pdfViewer.scrollPageIntoView({
-          pageNumber,
-          destArray: explicitDest,
-        });
-
-        if (this.pdfHistory) { // Update the browsing history, if enabled.
-          this.pdfHistory.push({
-            dest: explicitDest,
-            hash: namedDest,
-            page: pageNumber,
-          });
-        }
-      };
-
-      new Promise((resolve, reject) => {
-        if (typeof dest === 'string') {
-          this.pdfDocument.getDestination(dest).then((destArray) => {
-            resolve({
-              namedDest: dest,
-              explicitDest: destArray,
-            });
-          });
-          return;
-        }
-        resolve({
-          namedDest: '',
-          explicitDest: dest,
-        });
-      }).then((data) => {
-        if (!(data.explicitDest instanceof Array)) {
-          console.error(`PDFLinkService.navigateTo: "${data.explicitDest}" is` +
-                        ` not a valid destination array, for dest="${dest}".`);
-          return;
-        }
-        goToDestination(data);
-      });
-    },
-
-    /**
-     * @param {string|Array} dest - The PDF destination object.
-     * @returns {string} The hyperlink to the PDF object.
-     */
-    getDestinationHash(dest) {
-      if (typeof dest === 'string') {
-        return this.getAnchorUrl('#' + escape(dest));
-      }
-      if (dest instanceof Array) {
-        let str = JSON.stringify(dest);
-        return this.getAnchorUrl('#' + escape(str));
-      }
-      return this.getAnchorUrl('');
-    },
-
-    /**
-     * Prefix the full url on anchor links to make sure that links are resolved
-     * relative to the current URL instead of the one defined in <base href>.
-     * @param {String} anchor The anchor hash, including the #.
-     * @returns {string} The hyperlink to the PDF object.
-     */
-    getAnchorUrl: function PDFLinkService_getAnchorUrl(anchor) {
-      return (this.baseUrl || '') + anchor;
-    },
-
-    /**
-     * @param {string} hash
-     */
-    setHash: function PDFLinkService_setHash(hash) {
-      var pageNumber, dest;
-      if (hash.indexOf('=') >= 0) {
-        var params = parseQueryString(hash);
-        if ('search' in params) {
-          this.eventBus.dispatch('findfromurlhash', {
-            source: this,
-            query: params['search'].replace(/"/g, ''),
-            phraseSearch: (params['phrase'] === 'true'),
-          });
-        }
-        // borrowing syntax from "Parameters for Opening PDF Files"
-        if ('nameddest' in params) {
-          if (this.pdfHistory) {
-            this.pdfHistory.updateNextHashParam(params.nameddest);
-          }
-          this.navigateTo(params.nameddest);
-          return;
-        }
-        if ('page' in params) {
-          pageNumber = (params.page | 0) || 1;
-        }
-        if ('zoom' in params) {
-          // Build the destination array.
-          var zoomArgs = params.zoom.split(','); // scale,left,top
-          var zoomArg = zoomArgs[0];
-          var zoomArgNumber = parseFloat(zoomArg);
-
-          if (zoomArg.indexOf('Fit') === -1) {
-            // If the zoomArg is a number, it has to get divided by 100. If it's
-            // a string, it should stay as it is.
-            dest = [null, { name: 'XYZ', },
-                    zoomArgs.length > 1 ? (zoomArgs[1] | 0) : null,
-                    zoomArgs.length > 2 ? (zoomArgs[2] | 0) : null,
-                    (zoomArgNumber ? zoomArgNumber / 100 : zoomArg)];
-          } else {
-            if (zoomArg === 'Fit' || zoomArg === 'FitB') {
-              dest = [null, { name: zoomArg, }];
-            } else if ((zoomArg === 'FitH' || zoomArg === 'FitBH') ||
-                       (zoomArg === 'FitV' || zoomArg === 'FitBV')) {
-              dest = [null, { name: zoomArg, },
-                      zoomArgs.length > 1 ? (zoomArgs[1] | 0) : null];
-            } else if (zoomArg === 'FitR') {
-              if (zoomArgs.length !== 5) {
-                console.error('PDFLinkService_setHash: ' +
-                              'Not enough parameters for \'FitR\'.');
-              } else {
-                dest = [null, { name: zoomArg, },
-                        (zoomArgs[1] | 0), (zoomArgs[2] | 0),
-                        (zoomArgs[3] | 0), (zoomArgs[4] | 0)];
-              }
-            } else {
-              console.error('PDFLinkService_setHash: \'' + zoomArg +
-                            '\' is not a valid zoom value.');
-            }
-          }
-        }
-        if (dest) {
-          this.pdfViewer.scrollPageIntoView({
-            pageNumber: pageNumber || this.page,
-            destArray: dest,
-            allowNegativeOffset: true,
-          });
-        } else if (pageNumber) {
-          this.page = pageNumber; // simple page
-        }
-        if ('pagemode' in params) {
-          this.eventBus.dispatch('pagemode', {
-            source: this,
-            mode: params.pagemode,
-          });
-        }
-      } else { // Named (or explicit) destination.
-        if ((typeof PDFJSDev === 'undefined' || PDFJSDev.test('GENERIC')) &&
-            /^\d+$/.test(hash) && hash <= this.pagesCount) {
-          console.warn('PDFLinkService_setHash: specifying a page number ' +
-                       'directly after the hash symbol (#) is deprecated, ' +
-                       'please use the "#page=' + hash + '" form instead.');
-          this.page = hash | 0;
-        }
-
-        dest = unescape(hash);
-        try {
-          dest = JSON.parse(dest);
-
-          if (!(dest instanceof Array)) {
-            // Avoid incorrectly rejecting a valid named destination, such as
-            // e.g. "4.3" or "true", because `JSON.parse` converted its type.
-            dest = dest.toString();
-          }
-        } catch (ex) {}
-
-        if (typeof dest === 'string' || isValidExplicitDestination(dest)) {
-          if (this.pdfHistory) {
-            this.pdfHistory.updateNextHashParam(dest);
-          }
-          this.navigateTo(dest);
-          return;
-        }
-        console.error('PDFLinkService_setHash: \'' + unescape(hash) +
-                      '\' is not a valid destination.');
-      }
-    },
-
-    /**
-     * @param {string} action
-     */
-    executeNamedAction: function PDFLinkService_executeNamedAction(action) {
-      // See PDF reference, table 8.45 - Named action
-      switch (action) {
-        case 'GoBack':
-          if (this.pdfHistory) {
-            this.pdfHistory.back();
-          }
-          break;
-
-        case 'GoForward':
-          if (this.pdfHistory) {
-            this.pdfHistory.forward();
-          }
-          break;
-
-        case 'NextPage':
-          if (this.page < this.pagesCount) {
-            this.page++;
-          }
-          break;
-
-        case 'PrevPage':
-          if (this.page > 1) {
-            this.page--;
-          }
-          break;
-
-        case 'LastPage':
-          this.page = this.pagesCount;
-          break;
-
-        case 'FirstPage':
-          this.page = 1;
-          break;
-
-        default:
-          break; // No action according to spec
-      }
-
-      this.eventBus.dispatch('namedaction', {
-        source: this,
-        action,
-      });
-    },
-
-    /**
-     * @param {Object} params
-     */
-    onFileAttachmentAnnotation(params = {}) {
-      this.eventBus.dispatch('fileattachmentannotation', {
-        source: this,
-        id: params.id,
-        filename: params.filename,
-        content: params.content,
-      });
-    },
-
-    /**
-     * @param {number} pageNum - page number.
-     * @param {Object} pageRef - reference to the page.
-     */
-    cachePageRef: function PDFLinkService_cachePageRef(pageNum, pageRef) {
-      var refStr = pageRef.num + ' ' + pageRef.gen + ' R';
-      this._pagesRefCache[refStr] = pageNum;
-    },
-
-    _cachedPageNumber: function PDFLinkService_cachedPageNumber(pageRef) {
-      var refStr = pageRef.num + ' ' + pageRef.gen + ' R';
-      return (this._pagesRefCache && this._pagesRefCache[refStr]) || null;
-    },
-  };
-
-  function isValidExplicitDestination(dest) {
-    if (!(dest instanceof Array)) {
-      return false;
-    }
-    var destLength = dest.length, allowNull = true;
-    if (destLength < 2) {
-      return false;
-    }
-    var page = dest[0];
-    if (!(typeof page === 'object' &&
-          typeof page.num === 'number' && (page.num | 0) === page.num &&
-          typeof page.gen === 'number' && (page.gen | 0) === page.gen) &&
-        !(typeof page === 'number' && (page | 0) === page && page >= 0)) {
-      return false;
-    }
-    var zoom = dest[1];
-    if (!(typeof zoom === 'object' && typeof zoom.name === 'string')) {
-      return false;
-    }
-    switch (zoom.name) {
-      case 'XYZ':
-        if (destLength !== 5) {
-          return false;
-        }
-        break;
-      case 'Fit':
-      case 'FitB':
-        return destLength === 2;
-      case 'FitH':
-      case 'FitBH':
-      case 'FitV':
-      case 'FitBV':
-        if (destLength !== 3) {
-          return false;
-        }
-        break;
-      case 'FitR':
-        if (destLength !== 6) {
-          return false;
-        }
-        allowNull = false;
-        break;
-      default:
-        return false;
-    }
-    for (var i = 2; i < destLength; i++) {
-      var param = dest[i];
-      if (!(typeof param === 'number' || (allowNull && param === null))) {
-        return false;
-      }
-    }
-    return true;
+  setDocument(pdfDocument, baseUrl) {
+    this.baseUrl = baseUrl;
+    this.pdfDocument = pdfDocument;
+    this._pagesRefCache = Object.create(null);
   }
 
-  return PDFLinkService;
-})();
+  setViewer(pdfViewer) {
+    this.pdfViewer = pdfViewer;
+  }
 
-var SimpleLinkService = (function SimpleLinkServiceClosure() {
-  function SimpleLinkService() {}
+  setHistory(pdfHistory) {
+    this.pdfHistory = pdfHistory;
+  }
 
-  SimpleLinkService.prototype = {
-    /**
-     * @returns {number}
-     */
-    get page() {
-      return 0;
-    },
-    /**
-     * @param {number} value
-     */
-    set page(value) {},
-    /**
-     * @param dest - The PDF destination object.
-     */
-    navigateTo(dest) {},
-    /**
-     * @param dest - The PDF destination object.
-     * @returns {string} The hyperlink to the PDF object.
-     */
-    getDestinationHash(dest) {
-      return '#';
-    },
-    /**
-     * @param hash - The PDF parameters/hash.
-     * @returns {string} The hyperlink to the PDF object.
-     */
-    getAnchorUrl(hash) {
-      return '#';
-    },
-    /**
-     * @param {string} hash
-     */
-    setHash(hash) {},
-    /**
-     * @param {string} action
-     */
-    executeNamedAction(action) {},
-    /**
-     * @param {Object} params
-     */
-    onFileAttachmentAnnotation(params) {},
-    /**
-     * @param {number} pageNum - page number.
-     * @param {Object} pageRef - reference to the page.
-     */
-    cachePageRef(pageNum, pageRef) {},
-  };
-  return SimpleLinkService;
-})();
+  /**
+   * @returns {number}
+   */
+  get pagesCount() {
+    return this.pdfDocument ? this.pdfDocument.numPages : 0;
+  }
+
+  /**
+   * @returns {number}
+   */
+  get page() {
+    return this.pdfViewer.currentPageNumber;
+  }
+
+  /**
+   * @param {number} value
+   */
+  set page(value) {
+    this.pdfViewer.currentPageNumber = value;
+  }
+
+  /**
+   * @param {string|Array} dest - The named, or explicit, PDF destination.
+   */
+  navigateTo(dest) {
+    let goToDestination = ({ namedDest, explicitDest, }) => {
+      // Dest array looks like that: <page-ref> </XYZ|/FitXXX> <args..>
+      let destRef = explicitDest[0], pageNumber;
+
+      if (destRef instanceof Object) {
+        pageNumber = this._cachedPageNumber(destRef);
+
+        if (pageNumber === null) {
+          // Fetch the page reference if it's not yet available. This could
+          // only occur during loading, before all pages have been resolved.
+          this.pdfDocument.getPageIndex(destRef).then((pageIndex) => {
+            this.cachePageRef(pageIndex + 1, destRef);
+            goToDestination({ namedDest, explicitDest, });
+          }).catch(() => {
+            console.error(`PDFLinkService.navigateTo: "${destRef}" is not ` +
+                          `a valid page reference, for dest="${dest}".`);
+          });
+          return;
+        }
+      } else if ((destRef | 0) === destRef) { // Integer
+        pageNumber = destRef + 1;
+      } else {
+        console.error(`PDFLinkService.navigateTo: "${destRef}" is not ` +
+                      `a valid destination reference, for dest="${dest}".`);
+        return;
+      }
+      if (!pageNumber || pageNumber < 1 || pageNumber > this.pagesCount) {
+        console.error(`PDFLinkService.navigateTo: "${pageNumber}" is not ` +
+                      `a valid page number, for dest="${dest}".`);
+        return;
+      }
+
+      this.pdfViewer.scrollPageIntoView({
+        pageNumber,
+        destArray: explicitDest,
+      });
+
+      if (this.pdfHistory) { // Update the browsing history, if enabled.
+        this.pdfHistory.push({
+          dest: explicitDest,
+          hash: namedDest,
+          page: pageNumber,
+        });
+      }
+    };
+
+    new Promise((resolve, reject) => {
+      if (typeof dest === 'string') {
+        this.pdfDocument.getDestination(dest).then((destArray) => {
+          resolve({
+            namedDest: dest,
+            explicitDest: destArray,
+          });
+        });
+        return;
+      }
+      resolve({
+        namedDest: '',
+        explicitDest: dest,
+      });
+    }).then((data) => {
+      if (!(data.explicitDest instanceof Array)) {
+        console.error(`PDFLinkService.navigateTo: "${data.explicitDest}" is` +
+                      ` not a valid destination array, for dest="${dest}".`);
+        return;
+      }
+      goToDestination(data);
+    });
+  }
+
+  /**
+   * @param {string|Array} dest - The PDF destination object.
+   * @returns {string} The hyperlink to the PDF object.
+   */
+  getDestinationHash(dest) {
+    if (typeof dest === 'string') {
+      return this.getAnchorUrl('#' + escape(dest));
+    }
+    if (dest instanceof Array) {
+      let str = JSON.stringify(dest);
+      return this.getAnchorUrl('#' + escape(str));
+    }
+    return this.getAnchorUrl('');
+  }
+
+  /**
+   * Prefix the full url on anchor links to make sure that links are resolved
+   * relative to the current URL instead of the one defined in <base href>.
+   * @param {String} anchor The anchor hash, including the #.
+   * @returns {string} The hyperlink to the PDF object.
+   */
+  getAnchorUrl(anchor) {
+    return (this.baseUrl || '') + anchor;
+  }
+
+  /**
+   * @param {string} hash
+   */
+  setHash(hash) {
+    let pageNumber, dest;
+    if (hash.indexOf('=') >= 0) {
+      let params = parseQueryString(hash);
+      if ('search' in params) {
+        this.eventBus.dispatch('findfromurlhash', {
+          source: this,
+          query: params['search'].replace(/"/g, ''),
+          phraseSearch: (params['phrase'] === 'true'),
+        });
+      }
+      // borrowing syntax from "Parameters for Opening PDF Files"
+      if ('nameddest' in params) {
+        if (this.pdfHistory) {
+          this.pdfHistory.updateNextHashParam(params.nameddest);
+        }
+        this.navigateTo(params.nameddest);
+        return;
+      }
+      if ('page' in params) {
+        pageNumber = (params.page | 0) || 1;
+      }
+      if ('zoom' in params) {
+        // Build the destination array.
+        let zoomArgs = params.zoom.split(','); // scale,left,top
+        let zoomArg = zoomArgs[0];
+        let zoomArgNumber = parseFloat(zoomArg);
+
+        if (zoomArg.indexOf('Fit') === -1) {
+          // If the zoomArg is a number, it has to get divided by 100. If it's
+          // a string, it should stay as it is.
+          dest = [null, { name: 'XYZ', },
+                  zoomArgs.length > 1 ? (zoomArgs[1] | 0) : null,
+                  zoomArgs.length > 2 ? (zoomArgs[2] | 0) : null,
+                  (zoomArgNumber ? zoomArgNumber / 100 : zoomArg)];
+        } else {
+          if (zoomArg === 'Fit' || zoomArg === 'FitB') {
+            dest = [null, { name: zoomArg, }];
+          } else if ((zoomArg === 'FitH' || zoomArg === 'FitBH') ||
+                     (zoomArg === 'FitV' || zoomArg === 'FitBV')) {
+            dest = [null, { name: zoomArg, },
+                    zoomArgs.length > 1 ? (zoomArgs[1] | 0) : null];
+          } else if (zoomArg === 'FitR') {
+            if (zoomArgs.length !== 5) {
+              console.error(
+                'PDFLinkService.setHash: Not enough parameters for "FitR".');
+            } else {
+              dest = [null, { name: zoomArg, },
+                      (zoomArgs[1] | 0), (zoomArgs[2] | 0),
+                      (zoomArgs[3] | 0), (zoomArgs[4] | 0)];
+            }
+          } else {
+            console.error(`PDFLinkService.setHash: "${zoomArg}" is not ` +
+                          'a valid zoom value.');
+          }
+        }
+      }
+      if (dest) {
+        this.pdfViewer.scrollPageIntoView({
+          pageNumber: pageNumber || this.page,
+          destArray: dest,
+          allowNegativeOffset: true,
+        });
+      } else if (pageNumber) {
+        this.page = pageNumber; // simple page
+      }
+      if ('pagemode' in params) {
+        this.eventBus.dispatch('pagemode', {
+          source: this,
+          mode: params.pagemode,
+        });
+      }
+    } else { // Named (or explicit) destination.
+      if ((typeof PDFJSDev === 'undefined' || PDFJSDev.test('GENERIC')) &&
+          /^\d+$/.test(hash) && hash <= this.pagesCount) {
+        console.warn('PDFLinkService_setHash: specifying a page number ' +
+                     'directly after the hash symbol (#) is deprecated, ' +
+                     `please use the "#page=${hash}" form instead.`);
+        this.page = hash | 0;
+      }
+
+      dest = unescape(hash);
+      try {
+        dest = JSON.parse(dest);
+
+        if (!(dest instanceof Array)) {
+          // Avoid incorrectly rejecting a valid named destination, such as
+          // e.g. "4.3" or "true", because `JSON.parse` converted its type.
+          dest = dest.toString();
+        }
+      } catch (ex) {}
+
+      if (typeof dest === 'string' || isValidExplicitDestination(dest)) {
+        if (this.pdfHistory) {
+          this.pdfHistory.updateNextHashParam(dest);
+        }
+        this.navigateTo(dest);
+        return;
+      }
+      console.error(`PDFLinkService.setHash: "${unescape(hash)}" is not ` +
+                    'a valid destination.');
+    }
+  }
+
+  /**
+   * @param {string} action
+   */
+  executeNamedAction(action) {
+    // See PDF reference, table 8.45 - Named action
+    switch (action) {
+      case 'GoBack':
+        if (this.pdfHistory) {
+          this.pdfHistory.back();
+        }
+        break;
+
+      case 'GoForward':
+        if (this.pdfHistory) {
+          this.pdfHistory.forward();
+        }
+        break;
+
+      case 'NextPage':
+        if (this.page < this.pagesCount) {
+          this.page++;
+        }
+        break;
+
+      case 'PrevPage':
+        if (this.page > 1) {
+          this.page--;
+        }
+        break;
+
+      case 'LastPage':
+        this.page = this.pagesCount;
+        break;
+
+      case 'FirstPage':
+        this.page = 1;
+        break;
+
+      default:
+        break; // No action according to spec
+    }
+
+    this.eventBus.dispatch('namedaction', {
+      source: this,
+      action,
+    });
+  }
+
+  /**
+   * @param {Object} params
+   */
+  onFileAttachmentAnnotation({ id, filename, content, }) {
+    this.eventBus.dispatch('fileattachmentannotation', {
+      source: this,
+      id,
+      filename,
+      content,
+    });
+  }
+
+  /**
+   * @param {number} pageNum - page number.
+   * @param {Object} pageRef - reference to the page.
+   */
+  cachePageRef(pageNum, pageRef) {
+    let refStr = pageRef.num + ' ' + pageRef.gen + ' R';
+    this._pagesRefCache[refStr] = pageNum;
+  }
+
+  _cachedPageNumber(pageRef) {
+    let refStr = pageRef.num + ' ' + pageRef.gen + ' R';
+    return (this._pagesRefCache && this._pagesRefCache[refStr]) || null;
+  }
+}
+
+function isValidExplicitDestination(dest) {
+  if (!(dest instanceof Array)) {
+    return false;
+  }
+  let destLength = dest.length, allowNull = true;
+  if (destLength < 2) {
+    return false;
+  }
+  let page = dest[0];
+  if (!(typeof page === 'object' &&
+        typeof page.num === 'number' && (page.num | 0) === page.num &&
+        typeof page.gen === 'number' && (page.gen | 0) === page.gen) &&
+      !(typeof page === 'number' && (page | 0) === page && page >= 0)) {
+    return false;
+  }
+  let zoom = dest[1];
+  if (!(typeof zoom === 'object' && typeof zoom.name === 'string')) {
+    return false;
+  }
+  switch (zoom.name) {
+    case 'XYZ':
+      if (destLength !== 5) {
+        return false;
+      }
+      break;
+    case 'Fit':
+    case 'FitB':
+      return destLength === 2;
+    case 'FitH':
+    case 'FitBH':
+    case 'FitV':
+    case 'FitBV':
+      if (destLength !== 3) {
+        return false;
+      }
+      break;
+    case 'FitR':
+      if (destLength !== 6) {
+        return false;
+      }
+      allowNull = false;
+      break;
+    default:
+      return false;
+  }
+  for (let i = 2; i < destLength; i++) {
+    let param = dest[i];
+    if (!(typeof param === 'number' || (allowNull && param === null))) {
+      return false;
+    }
+  }
+  return true;
+}
+
+class SimpleLinkService {
+  /**
+   * @returns {number}
+   */
+  get page() {
+    return 0;
+  }
+  /**
+   * @param {number} value
+   */
+  set page(value) {}
+  /**
+   * @param dest - The PDF destination object.
+   */
+  navigateTo(dest) {}
+  /**
+   * @param dest - The PDF destination object.
+   * @returns {string} The hyperlink to the PDF object.
+   */
+  getDestinationHash(dest) {
+    return '#';
+  }
+  /**
+   * @param hash - The PDF parameters/hash.
+   * @returns {string} The hyperlink to the PDF object.
+   */
+  getAnchorUrl(hash) {
+    return '#';
+  }
+  /**
+   * @param {string} hash
+   */
+  setHash(hash) {}
+  /**
+   * @param {string} action
+   */
+  executeNamedAction(action) {}
+  /**
+   * @param {Object} params
+   */
+  onFileAttachmentAnnotation({ id, filename, content, }) {}
+  /**
+   * @param {number} pageNum - page number.
+   * @param {Object} pageRef - reference to the page.
+   */
+  cachePageRef(pageNum, pageRef) {}
+}
 
 export {
   PDFLinkService,