From facefb0c7910fe303325d96c35bdb0dbf91d7b6c Mon Sep 17 00:00:00 2001
From: Yury Delendik <ydelendik@mozilla.com>
Date: Thu, 23 Feb 2017 16:35:35 -0600
Subject: [PATCH] Move compatibility code to the shared/compatibility.js.

---
 examples/components/pageviewer.html   |    3 -
 examples/components/simpleviewer.html |    3 -
 examples/learning/helloworld64.html   |    3 -
 gulpfile.js                           |   19 +-
 make.js                               |    1 -
 src/core/worker.js                    |   42 -
 src/shared/compatibility.js           | 1775 +++++++++++++++++++++++++
 src/shared/util.js                    | 1015 +-------------
 web/compatibility.js                  |  653 +--------
 web/viewer.html                       |    4 +-
 10 files changed, 1801 insertions(+), 1717 deletions(-)
 create mode 100644 src/shared/compatibility.js

diff --git a/examples/components/pageviewer.html b/examples/components/pageviewer.html
index aee52ad09..6e57aa39b 100644
--- a/examples/components/pageviewer.html
+++ b/examples/components/pageviewer.html
@@ -31,9 +31,6 @@ limitations under the License.
 
   <link rel="stylesheet" href="../../build/dist/web/pdf_viewer.css">
 
-  <!-- for legacy browsers -->
-  <script src="../../build/dist/web/compatibility.js"></script>
-
   <script src="../../build/dist/build/pdf.js"></script>
   <script src="../../build/dist/web/pdf_viewer.js"></script>
 </head>
diff --git a/examples/components/simpleviewer.html b/examples/components/simpleviewer.html
index 25dc7b71f..692d1ee04 100644
--- a/examples/components/simpleviewer.html
+++ b/examples/components/simpleviewer.html
@@ -37,9 +37,6 @@ limitations under the License.
 
   <link rel="stylesheet" href="../../build/dist/web/pdf_viewer.css">
 
-  <!-- for legacy browsers -->
-  <script src="../../build/dist/web/compatibility.js"></script>
-
   <script src="../../build/dist/build/pdf.js"></script>
   <script src="../../build/dist/web/pdf_viewer.js"></script>
 </head>
diff --git a/examples/learning/helloworld64.html b/examples/learning/helloworld64.html
index 891c2351d..0b2f09d0e 100644
--- a/examples/learning/helloworld64.html
+++ b/examples/learning/helloworld64.html
@@ -10,9 +10,6 @@
 
 <canvas id="the-canvas" style="border:1px  solid black"></canvas>
 
-<!-- for legacy browsers we need to use compatibility.js -->
-<script src="../../web/compatibility.js"></script>
-
 <script src="../../build/generic/build/pdf.js"></script>
 
 <script id="script">
diff --git a/gulpfile.js b/gulpfile.js
index 9d7d245e6..4a8cb76df 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -245,6 +245,20 @@ function createComponentsBundle(defines) {
     .pipe(replaceJSRootName(componentsAMDName));
 }
 
+function createCompatibilityBundle(defines) {
+  var compatibilityAMDName = 'pdfjs-dist/web/compatibility';
+  var compatibilityOutputName = 'compatibility.js';
+
+  var compatibilityFileConfig = createWebpackConfig(defines, {
+    filename: compatibilityOutputName,
+    library: compatibilityAMDName,
+    libraryTarget: 'umd',
+    umdNamedDefine: true
+  });
+  return gulp.src('./web/compatibility.js')
+    .pipe(webpack2Stream(compatibilityFileConfig));
+}
+
 function checkFile(path) {
   try {
     var stat = fs.lstatSync(path);
@@ -536,8 +550,7 @@ gulp.task('generic', ['buildnumber', 'locale'], function () {
         .pipe(gulp.dest(GENERIC_DIR + 'web')),
     gulp.src('LICENSE').pipe(gulp.dest(GENERIC_DIR)),
     gulp.src([
-      'external/webL10n/l10n.js',
-      'web/compatibility.js'
+      'external/webL10n/l10n.js'
     ]).pipe(gulp.dest(GENERIC_DIR + 'web')),
     gulp.src([
       'web/locale/*/viewer.properties',
@@ -572,8 +585,8 @@ gulp.task('components', ['buildnumber'], function () {
 
   return merge([
     createComponentsBundle(defines).pipe(gulp.dest(COMPONENTS_DIR)),
+    createCompatibilityBundle(defines).pipe(gulp.dest(COMPONENTS_DIR)),
     gulp.src(COMPONENTS_IMAGES).pipe(gulp.dest(COMPONENTS_DIR + 'images')),
-    gulp.src('web/compatibility.js').pipe(gulp.dest(COMPONENTS_DIR)),
     preprocessCSS('web/pdf_viewer.css', 'components', defines, true)
         .pipe(gulp.dest(COMPONENTS_DIR)),
   ]);
diff --git a/make.js b/make.js
index b3e1e2ca0..441a772bb 100644
--- a/make.js
+++ b/make.js
@@ -298,7 +298,6 @@ target.minified = function() {
 
 target.minifiedpost = function () {
   var viewerFiles = [
-    'web/compatibility.js',
     'external/webL10n/l10n.js',
     MINIFIED_DIR + BUILD_DIR + 'pdf.js',
     MINIFIED_DIR + '/web/viewer.js'
diff --git a/src/core/worker.js b/src/core/worker.js
index 4f4b7a1c1..756c9e8a6 100644
--- a/src/core/worker.js
+++ b/src/core/worker.js
@@ -41,7 +41,6 @@ var arrayByteLength = sharedUtil.arrayByteLength;
 var arraysToBytes = sharedUtil.arraysToBytes;
 var assert = sharedUtil.assert;
 var createPromiseCapability = sharedUtil.createPromiseCapability;
-var error = sharedUtil.error;
 var info = sharedUtil.info;
 var warn = sharedUtil.warn;
 var setVerbosityLevel = sharedUtil.setVerbosityLevel;
@@ -49,7 +48,6 @@ var isNodeJS = sharedUtil.isNodeJS;
 var Ref = corePrimitives.Ref;
 var LocalPdfManager = corePdfManager.LocalPdfManager;
 var NetworkPdfManager = corePdfManager.NetworkPdfManager;
-var globalScope = sharedUtil.globalScope;
 
 var WorkerTask = (function WorkerTaskClosure() {
   function WorkerTask(name) {
@@ -962,46 +960,6 @@ var WorkerMessageHandler = {
 };
 
 function initializeWorker() {
-  if ((typeof PDFJSDev === 'undefined' || !PDFJSDev.test('MOZCENTRAL')) &&
-      !('console' in globalScope)) {
-    var consoleTimer = {};
-
-    var workerConsole = {
-      log: function log() {
-        var args = Array.prototype.slice.call(arguments);
-        globalScope.postMessage({
-          targetName: 'main',
-          action: 'console_log',
-          data: args
-        });
-      },
-
-      error: function error() {
-        var args = Array.prototype.slice.call(arguments);
-        globalScope.postMessage({
-          targetName: 'main',
-          action: 'console_error',
-          data: args
-        });
-        throw 'pdf.js execution error';
-      },
-
-      time: function time(name) {
-        consoleTimer[name] = Date.now();
-      },
-
-      timeEnd: function timeEnd(name) {
-        var time = consoleTimer[name];
-        if (!time) {
-          error('Unknown timer name ' + name);
-        }
-        this.log('Timer:', name, Date.now() - time);
-      }
-    };
-
-    globalScope.console = workerConsole;
-  }
-
   var handler = new MessageHandler('worker', 'main', self);
   WorkerMessageHandler.setup(handler, self);
   handler.send('ready', null);
diff --git a/src/shared/compatibility.js b/src/shared/compatibility.js
new file mode 100644
index 000000000..ada8d4e4f
--- /dev/null
+++ b/src/shared/compatibility.js
@@ -0,0 +1,1775 @@
+/* Copyright 2017 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/* eslint-disable no-extend-native */
+/* globals VBArray, PDFJS, global */
+
+'use strict';
+
+(function (root, factory) {
+  if (typeof define === 'function' && define.amd) {
+    define('pdfjs/shared/compatibility', ['exports'], factory);
+  } else if (typeof exports !== 'undefined') {
+    factory(exports);
+  } else {
+    factory((root.pdfjsSharedCompatibility = {}));
+  }
+}(this, function (exports) {
+
+// Skip compatibility checks for the extensions and if we already run
+// this module.
+if ((typeof PDFJSDev === 'undefined' ||
+     !PDFJSDev.test('FIREFOX || MOZCENTRAL || CHROME')) &&
+    (typeof PDFJS === 'undefined' || !PDFJS.compatibilityChecked)) {
+
+var globalScope = (typeof window !== 'undefined') ? window :
+                  (typeof global !== 'undefined') ? global :
+                  (typeof self !== 'undefined') ? self : this;
+
+var userAgent = (typeof navigator !== 'undefined' && navigator.userAgent) || '';
+var isAndroid = /Android/.test(userAgent);
+var isAndroidPre3 = /Android\s[0-2][^\d]/.test(userAgent);
+var isAndroidPre5 = /Android\s[0-4][^\d]/.test(userAgent);
+var isChrome = userAgent.indexOf('Chrom') >= 0;
+var isChromeWithRangeBug = /Chrome\/(39|40)\./.test(userAgent);
+var isIOSChrome = userAgent.indexOf('CriOS') >= 0;
+var isIE = userAgent.indexOf('Trident') >= 0;
+var isIOS = /\b(iPad|iPhone|iPod)(?=;)/.test(userAgent);
+var isOpera = userAgent.indexOf('Opera') >= 0;
+var isSafari = /Safari\//.test(userAgent) &&
+               !/(Chrome\/|Android\s)/.test(userAgent);
+
+var hasDOM = typeof window === 'object' && typeof document === 'object';
+
+// Initializing PDFJS global object here, it case if we need to change/disable
+// some PDF.js features, e.g. range requests
+if (typeof PDFJS === 'undefined') {
+  globalScope.PDFJS = {};
+}
+
+PDFJS.compatibilityChecked = true;
+
+// Checking if the typed arrays are supported
+// Support: iOS<6.0 (subarray), IE<10, Android<4.0
+(function checkTypedArrayCompatibility() {
+  if (typeof Uint8Array !== 'undefined') {
+    // Support: iOS<6.0
+    if (typeof Uint8Array.prototype.subarray === 'undefined') {
+      Uint8Array.prototype.subarray = function subarray(start, end) {
+        return new Uint8Array(this.slice(start, end));
+      };
+      Float32Array.prototype.subarray = function subarray(start, end) {
+        return new Float32Array(this.slice(start, end));
+      };
+    }
+
+    // Support: Android<4.1
+    if (typeof Float64Array === 'undefined') {
+      globalScope.Float64Array = Float32Array;
+    }
+    return;
+  }
+
+  function subarray(start, end) {
+    return new TypedArray(this.slice(start, end));
+  }
+
+  function setArrayOffset(array, offset) {
+    if (arguments.length < 2) {
+      offset = 0;
+    }
+    for (var i = 0, n = array.length; i < n; ++i, ++offset) {
+      this[offset] = array[i] & 0xFF;
+    }
+  }
+
+  function TypedArray(arg1) {
+    var result, i, n;
+    if (typeof arg1 === 'number') {
+      result = [];
+      for (i = 0; i < arg1; ++i) {
+        result[i] = 0;
+      }
+    } else if ('slice' in arg1) {
+      result = arg1.slice(0);
+    } else {
+      result = [];
+      for (i = 0, n = arg1.length; i < n; ++i) {
+        result[i] = arg1[i];
+      }
+    }
+
+    result.subarray = subarray;
+    result.buffer = result;
+    result.byteLength = result.length;
+    result.set = setArrayOffset;
+
+    if (typeof arg1 === 'object' && arg1.buffer) {
+      result.buffer = arg1.buffer;
+    }
+    return result;
+  }
+
+  globalScope.Uint8Array = TypedArray;
+  globalScope.Int8Array = TypedArray;
+
+  // we don't need support for set, byteLength for 32-bit array
+  // so we can use the TypedArray as well
+  globalScope.Uint32Array = TypedArray;
+  globalScope.Int32Array = TypedArray;
+  globalScope.Uint16Array = TypedArray;
+  globalScope.Float32Array = TypedArray;
+  globalScope.Float64Array = TypedArray;
+})();
+
+// URL = URL || webkitURL
+// Support: Safari<7, Android 4.2+
+(function normalizeURLObject() {
+  if (!globalScope.URL) {
+    globalScope.URL = globalScope.webkitURL;
+  }
+})();
+
+// Object.defineProperty()?
+// Support: Android<4.0, Safari<5.1
+(function checkObjectDefinePropertyCompatibility() {
+  if (typeof Object.defineProperty !== 'undefined') {
+    var definePropertyPossible = true;
+    try {
+      if (hasDOM) {
+        // some browsers (e.g. safari) cannot use defineProperty() on DOM
+        // objects and thus the native version is not sufficient
+        Object.defineProperty(new Image(), 'id', { value: 'test' });
+      }
+      // ... another test for android gb browser for non-DOM objects
+      var Test = function Test() {};
+      Test.prototype = { get id() { } };
+      Object.defineProperty(new Test(), 'id',
+        { value: '', configurable: true, enumerable: true, writable: false });
+    } catch (e) {
+      definePropertyPossible = false;
+    }
+    if (definePropertyPossible) {
+      return;
+    }
+  }
+
+  Object.defineProperty = function objectDefineProperty(obj, name, def) {
+    delete obj[name];
+    if ('get' in def) {
+      obj.__defineGetter__(name, def['get']);
+    }
+    if ('set' in def) {
+      obj.__defineSetter__(name, def['set']);
+    }
+    if ('value' in def) {
+      obj.__defineSetter__(name, function objectDefinePropertySetter(value) {
+        this.__defineGetter__(name, function objectDefinePropertyGetter() {
+          return value;
+        });
+        return value;
+      });
+      obj[name] = def.value;
+    }
+  };
+})();
+
+
+// No XMLHttpRequest#response?
+// Support: IE<11, Android <4.0
+(function checkXMLHttpRequestResponseCompatibility() {
+  if (typeof XMLHttpRequest === 'undefined') {
+    return;
+  }
+  var xhrPrototype = XMLHttpRequest.prototype;
+  var xhr = new XMLHttpRequest();
+  if (!('overrideMimeType' in xhr)) {
+    // IE10 might have response, but not overrideMimeType
+    // Support: IE10
+    Object.defineProperty(xhrPrototype, 'overrideMimeType', {
+      value: function xmlHttpRequestOverrideMimeType(mimeType) {}
+    });
+  }
+  if ('responseType' in xhr) {
+    return;
+  }
+
+  Object.defineProperty(xhrPrototype, 'responseType', {
+    get: function xmlHttpRequestGetResponseType() {
+      return this._responseType || 'text';
+    },
+    set: function xmlHttpRequestSetResponseType(value) {
+      if (value === 'text' || value === 'arraybuffer') {
+        this._responseType = value;
+        if (value === 'arraybuffer' &&
+            typeof this.overrideMimeType === 'function') {
+          this.overrideMimeType('text/plain; charset=x-user-defined');
+        }
+      }
+    }
+  });
+
+  // Support: IE9
+  if (typeof VBArray !== 'undefined') {
+    Object.defineProperty(xhrPrototype, 'response', {
+      get: function xmlHttpRequestResponseGet() {
+        if (this.responseType === 'arraybuffer') {
+          return new Uint8Array(new VBArray(this.responseBody).toArray());
+        }
+        return this.responseText;
+      }
+    });
+    return;
+  }
+
+  Object.defineProperty(xhrPrototype, 'response', {
+    get: function xmlHttpRequestResponseGet() {
+      if (this.responseType !== 'arraybuffer') {
+        return this.responseText;
+      }
+      var text = this.responseText;
+      var i, n = text.length;
+      var result = new Uint8Array(n);
+      for (i = 0; i < n; ++i) {
+        result[i] = text.charCodeAt(i) & 0xFF;
+      }
+      return result.buffer;
+    }
+  });
+})();
+
+// window.btoa (base64 encode function) ?
+// Support: IE<10
+(function checkWindowBtoaCompatibility() {
+  if ('btoa' in globalScope) {
+    return;
+  }
+
+  var digits =
+    'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
+
+  globalScope.btoa = function (chars) {
+    var buffer = '';
+    var i, n;
+    for (i = 0, n = chars.length; i < n; i += 3) {
+      var b1 = chars.charCodeAt(i) & 0xFF;
+      var b2 = chars.charCodeAt(i + 1) & 0xFF;
+      var b3 = chars.charCodeAt(i + 2) & 0xFF;
+      var d1 = b1 >> 2, d2 = ((b1 & 3) << 4) | (b2 >> 4);
+      var d3 = i + 1 < n ? ((b2 & 0xF) << 2) | (b3 >> 6) : 64;
+      var d4 = i + 2 < n ? (b3 & 0x3F) : 64;
+      buffer += (digits.charAt(d1) + digits.charAt(d2) +
+                 digits.charAt(d3) + digits.charAt(d4));
+    }
+    return buffer;
+  };
+})();
+
+// window.atob (base64 encode function)?
+// Support: IE<10
+(function checkWindowAtobCompatibility() {
+  if ('atob' in globalScope) {
+    return;
+  }
+
+  // https://github.com/davidchambers/Base64.js
+  var digits =
+    'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
+  globalScope.atob = function (input) {
+    input = input.replace(/=+$/, '');
+    if (input.length % 4 === 1) {
+      throw new Error('bad atob input');
+    }
+    for (
+      // initialize result and counters
+      var bc = 0, bs, buffer, idx = 0, output = '';
+      // get next character
+      (buffer = input.charAt(idx++));
+      // character found in table?
+      // initialize bit storage and add its ascii value
+      ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer,
+        // and if not first of each 4 characters,
+        // convert the first 8 bits to one ascii character
+        bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0
+    ) {
+      // try to find character in table (0-63, not found => -1)
+      buffer = digits.indexOf(buffer);
+    }
+    return output;
+  };
+})();
+
+// Function.prototype.bind?
+// Support: Android<4.0, iOS<6.0
+(function checkFunctionPrototypeBindCompatibility() {
+  if (typeof Function.prototype.bind !== 'undefined') {
+    return;
+  }
+
+  Function.prototype.bind = function functionPrototypeBind(obj) {
+    var fn = this, headArgs = Array.prototype.slice.call(arguments, 1);
+    var bound = function functionPrototypeBindBound() {
+      var args = headArgs.concat(Array.prototype.slice.call(arguments));
+      return fn.apply(obj, args);
+    };
+    return bound;
+  };
+})();
+
+// HTMLElement dataset property
+// Support: IE<11, Safari<5.1, Android<4.0
+(function checkDatasetProperty() {
+  if (!hasDOM) {
+    return;
+  }
+  var div = document.createElement('div');
+  if ('dataset' in div) {
+    return; // dataset property exists
+  }
+
+  Object.defineProperty(HTMLElement.prototype, 'dataset', {
+    get: function() {
+      if (this._dataset) {
+        return this._dataset;
+      }
+
+      var dataset = {};
+      for (var j = 0, jj = this.attributes.length; j < jj; j++) {
+        var attribute = this.attributes[j];
+        if (attribute.name.substring(0, 5) !== 'data-') {
+          continue;
+        }
+        var key = attribute.name.substring(5).replace(/\-([a-z])/g,
+          function(all, ch) {
+            return ch.toUpperCase();
+          });
+        dataset[key] = attribute.value;
+      }
+
+      Object.defineProperty(this, '_dataset', {
+        value: dataset,
+        writable: false,
+        enumerable: false
+      });
+      return dataset;
+    },
+    enumerable: true
+  });
+})();
+
+// HTMLElement classList property
+// Support: IE<10, Android<4.0, iOS<5.0
+(function checkClassListProperty() {
+  function changeList(element, itemName, add, remove) {
+    var s = element.className || '';
+    var list = s.split(/\s+/g);
+    if (list[0] === '') {
+      list.shift();
+    }
+    var index = list.indexOf(itemName);
+    if (index < 0 && add) {
+      list.push(itemName);
+    }
+    if (index >= 0 && remove) {
+      list.splice(index, 1);
+    }
+    element.className = list.join(' ');
+    return (index >= 0);
+  }
+
+  if (!hasDOM) {
+    return;
+  }
+
+  var div = document.createElement('div');
+  if ('classList' in div) {
+    return; // classList property exists
+  }
+
+  var classListPrototype = {
+    add: function(name) {
+      changeList(this.element, name, true, false);
+    },
+    contains: function(name) {
+      return changeList(this.element, name, false, false);
+    },
+    remove: function(name) {
+      changeList(this.element, name, false, true);
+    },
+    toggle: function(name) {
+      changeList(this.element, name, true, true);
+    }
+  };
+
+  Object.defineProperty(HTMLElement.prototype, 'classList', {
+    get: function() {
+      if (this._classList) {
+        return this._classList;
+      }
+
+      var classList = Object.create(classListPrototype, {
+        element: {
+          value: this,
+          writable: false,
+          enumerable: true
+        }
+      });
+      Object.defineProperty(this, '_classList', {
+        value: classList,
+        writable: false,
+        enumerable: false
+      });
+      return classList;
+    },
+    enumerable: true
+  });
+})();
+
+// Checking if worker has console support. Forwarding all messages to the main
+// thread if console object is absent.
+(function checkWorkerConsoleCompatibility() {
+  if (typeof importScripts === 'undefined' || 'console' in globalScope) {
+    return;
+  }
+
+  var consoleTimer = {};
+
+  var workerConsole = {
+    log: function log() {
+      var args = Array.prototype.slice.call(arguments);
+      globalScope.postMessage({
+        targetName: 'main',
+        action: 'console_log',
+        data: args
+      });
+    },
+
+    error: function error() {
+      var args = Array.prototype.slice.call(arguments);
+      globalScope.postMessage({
+        targetName: 'main',
+        action: 'console_error',
+        data: args
+      });
+    },
+
+    time: function time(name) {
+      consoleTimer[name] = Date.now();
+    },
+
+    timeEnd: function timeEnd(name) {
+      var time = consoleTimer[name];
+      if (!time) {
+        throw new Error('Unknown timer name ' + name);
+      }
+      this.log('Timer:', name, Date.now() - time);
+    }
+  };
+
+  globalScope.console = workerConsole;
+})();
+
+// Check console compatibility
+// In older IE versions the console object is not available
+// unless console is open.
+// Support: IE<10
+(function checkConsoleCompatibility() {
+  if (!hasDOM) {
+    return;
+  }
+  if (!('console' in window)) {
+    window.console = {
+      log: function() {},
+      error: function() {},
+      warn: function() {}
+    };
+    return;
+  }
+  if (!('bind' in console.log)) {
+    // native functions in IE9 might not have bind
+    console.log = (function(fn) {
+      return function(msg) {
+        return fn(msg);
+      };
+    })(console.log);
+    console.error = (function(fn) {
+      return function(msg) {
+        return fn(msg);
+      };
+    })(console.error);
+    console.warn = (function(fn) {
+      return function(msg) {
+        return fn(msg);
+      };
+    })(console.warn);
+    return;
+  }
+})();
+
+// Check onclick compatibility in Opera
+// Support: Opera<15
+(function checkOnClickCompatibility() {
+  // workaround for reported Opera bug DSK-354448:
+  // onclick fires on disabled buttons with opaque content
+  function ignoreIfTargetDisabled(event) {
+    if (isDisabled(event.target)) {
+      event.stopPropagation();
+    }
+  }
+  function isDisabled(node) {
+    return node.disabled || (node.parentNode && isDisabled(node.parentNode));
+  }
+  if (isOpera) {
+    // use browser detection since we cannot feature-check this bug
+    document.addEventListener('click', ignoreIfTargetDisabled, true);
+  }
+})();
+
+// Checks if possible to use URL.createObjectURL()
+// Support: IE, Chrome on iOS
+(function checkOnBlobSupport() {
+  // sometimes IE and Chrome on iOS loosing the data created with
+  // createObjectURL(), see #3977 and #8081
+  if (isIE || isIOSChrome) {
+    PDFJS.disableCreateObjectURL = true;
+  }
+})();
+
+// Checks if navigator.language is supported
+(function checkNavigatorLanguage() {
+  if (typeof navigator === 'undefined') {
+    return;
+  }
+  if ('language' in navigator) {
+    return;
+  }
+  PDFJS.locale = navigator.userLanguage || 'en-US';
+})();
+
+// Support: Safari 6.0+, Android<3.0, Chrome 39/40, iOS
+(function checkRangeRequests() {
+  // Safari has issues with cached range requests see:
+  // https://github.com/mozilla/pdf.js/issues/3260
+  // Last tested with version 6.0.4.
+
+  // Older versions of Android (pre 3.0) has issues with range requests, see:
+  // https://github.com/mozilla/pdf.js/issues/3381.
+  // Make sure that we only match webkit-based Android browsers,
+  // since Firefox/Fennec works as expected.
+
+  // Range requests are broken in Chrome 39 and 40, https://crbug.com/442318
+  if (isSafari || isAndroidPre3 || isChromeWithRangeBug || isIOS) {
+    PDFJS.disableRange = true;
+    PDFJS.disableStream = true;
+  }
+})();
+
+// Check if the browser supports manipulation of the history.
+// Support: IE<10, Android<4.2
+(function checkHistoryManipulation() {
+  if (!hasDOM) {
+    return;
+  }
+  // Android 2.x has so buggy pushState support that it was removed in
+  // Android 3.0 and restored as late as in Android 4.2.
+  // Support: Android 2.x
+  if (!history.pushState || isAndroidPre3) {
+    PDFJS.disableHistory = true;
+  }
+})();
+
+// Support: IE<11, Chrome<21, Android<4.4, Safari<6
+(function checkSetPresenceInImageData() {
+  if (!hasDOM) {
+    return;
+  }
+  // IE < 11 will use window.CanvasPixelArray which lacks set function.
+  if (window.CanvasPixelArray) {
+    if (typeof window.CanvasPixelArray.prototype.set !== 'function') {
+      window.CanvasPixelArray.prototype.set = function(arr) {
+        for (var i = 0, ii = this.length; i < ii; i++) {
+          this[i] = arr[i];
+        }
+      };
+    }
+  } else {
+    // Old Chrome and Android use an inaccessible CanvasPixelArray prototype.
+    // Because we cannot feature detect it, we rely on user agent parsing.
+    var polyfill = false, versionMatch;
+    if (isChrome) {
+      versionMatch = userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
+      // Chrome < 21 lacks the set function.
+      polyfill = versionMatch && parseInt(versionMatch[2]) < 21;
+    } else if (isAndroid) {
+      // Android < 4.4 lacks the set function.
+      // Android >= 4.4 will contain Chrome in the user agent,
+      // thus pass the Chrome check above and not reach this block.
+      polyfill = isAndroidPre5;
+    } else if (isSafari) {
+      versionMatch = userAgent.
+        match(/Version\/([0-9]+)\.([0-9]+)\.([0-9]+) Safari\//);
+      // Safari < 6 lacks the set function.
+      polyfill = versionMatch && parseInt(versionMatch[1]) < 6;
+    }
+
+    if (polyfill) {
+      var contextPrototype = window.CanvasRenderingContext2D.prototype;
+      var createImageData = contextPrototype.createImageData;
+      contextPrototype.createImageData = function(w, h) {
+        var imageData = createImageData.call(this, w, h);
+        imageData.data.set = function(arr) {
+          for (var i = 0, ii = this.length; i < ii; i++) {
+            this[i] = arr[i];
+          }
+        };
+        return imageData;
+      };
+      // this closure will be kept referenced, so clear its vars
+      contextPrototype = null;
+    }
+  }
+})();
+
+// Support: IE<10, Android<4.0, iOS
+(function checkRequestAnimationFrame() {
+  function fakeRequestAnimationFrame(callback) {
+    window.setTimeout(callback, 20);
+  }
+
+  if (!hasDOM) {
+    return;
+  }
+  if (isIOS) {
+    // requestAnimationFrame on iOS is broken, replacing with fake one.
+    window.requestAnimationFrame = fakeRequestAnimationFrame;
+    return;
+  }
+  if ('requestAnimationFrame' in window) {
+    return;
+  }
+  window.requestAnimationFrame =
+    window.mozRequestAnimationFrame ||
+    window.webkitRequestAnimationFrame ||
+    fakeRequestAnimationFrame;
+})();
+
+// Support: Android, iOS
+(function checkCanvasSizeLimitation() {
+  if (isIOS || isAndroid) {
+    // 5MP
+    PDFJS.maxCanvasPixels = 5242880;
+  }
+})();
+
+// Disable fullscreen support for certain problematic configurations.
+// Support: IE11+ (when embedded).
+(function checkFullscreenSupport() {
+  if (!hasDOM) {
+    return;
+  }
+  if (isIE && window.parent !== window) {
+    PDFJS.disableFullscreen = true;
+  }
+})();
+
+// Provides document.currentScript support
+// Support: IE, Chrome<29.
+(function checkCurrentScript() {
+  if (!hasDOM) {
+    return;
+  }
+  if ('currentScript' in document) {
+    return;
+  }
+  Object.defineProperty(document, 'currentScript', {
+    get: function () {
+      var scripts = document.getElementsByTagName('script');
+      return scripts[scripts.length - 1];
+    },
+    enumerable: true,
+    configurable: true
+  });
+})();
+
+// Provides `input.type = 'type'` runtime failure protection.
+// Support: IE9,10.
+(function checkInputTypeNumberAssign() {
+  if (!hasDOM) {
+    return;
+  }
+  var el = document.createElement('input');
+  try {
+    el.type = 'number';
+  } catch (ex) {
+    var inputProto = el.constructor.prototype;
+    var typeProperty = Object.getOwnPropertyDescriptor(inputProto, 'type');
+    Object.defineProperty(inputProto, 'type', {
+      get: function () {
+        return typeProperty.get.call(this);
+      },
+      set: function (value) {
+        typeProperty.set.call(this, value === 'number' ? 'text' : value);
+      },
+      enumerable: true,
+      configurable: true
+    });
+  }
+})();
+
+// Provides correct document.readyState value for legacy browsers.
+// Support: IE9,10.
+(function checkDocumentReadyState() {
+  if (!hasDOM) {
+    return;
+  }
+  if (!document.attachEvent) {
+    return;
+  }
+  var documentProto = document.constructor.prototype;
+  var readyStateProto = Object.getOwnPropertyDescriptor(documentProto,
+                                                        'readyState');
+  Object.defineProperty(documentProto, 'readyState', {
+    get: function () {
+      var value = readyStateProto.get.call(this);
+      return value === 'interactive' ? 'loading' : value;
+    },
+    set: function (value) {
+      readyStateProto.set.call(this, value);
+    },
+    enumerable: true,
+    configurable: true
+  });
+})();
+
+// Provides support for ChildNode.remove in legacy browsers.
+// Support: IE.
+(function checkChildNodeRemove() {
+  if (!hasDOM) {
+    return;
+  }
+  if (typeof Element.prototype.remove !== 'undefined') {
+    return;
+  }
+  Element.prototype.remove = function () {
+    if (this.parentNode) {
+      this.parentNode.removeChild(this);
+    }
+  };
+})();
+
+/**
+ * Polyfill for Promises:
+ * The following promise implementation tries to generally implement the
+ * Promise/A+ spec. Some notable differences from other promise libraries are:
+ * - There currently isn't a separate deferred and promise object.
+ * - Unhandled rejections eventually show an error if they aren't handled.
+ *
+ * Based off of the work in:
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=810490
+ */
+(function checkPromise() {
+  if (globalScope.Promise) {
+    // Promises existing in the DOM/Worker, checking presence of all/resolve
+    if (typeof globalScope.Promise.all !== 'function') {
+      globalScope.Promise.all = function (iterable) {
+        var count = 0, results = [], resolve, reject;
+        var promise = new globalScope.Promise(function (resolve_, reject_) {
+          resolve = resolve_;
+          reject = reject_;
+        });
+        iterable.forEach(function (p, i) {
+          count++;
+          p.then(function (result) {
+            results[i] = result;
+            count--;
+            if (count === 0) {
+              resolve(results);
+            }
+          }, reject);
+        });
+        if (count === 0) {
+          resolve(results);
+        }
+        return promise;
+      };
+    }
+    if (typeof globalScope.Promise.resolve !== 'function') {
+      globalScope.Promise.resolve = function (value) {
+        return new globalScope.Promise(function (resolve) {
+          resolve(value);
+        });
+      };
+    }
+    if (typeof globalScope.Promise.reject !== 'function') {
+      globalScope.Promise.reject = function (reason) {
+        return new globalScope.Promise(function (resolve, reject) {
+          reject(reason);
+        });
+      };
+    }
+    if (typeof globalScope.Promise.prototype.catch !== 'function') {
+      globalScope.Promise.prototype.catch = function (onReject) {
+        return globalScope.Promise.prototype.then(undefined, onReject);
+      };
+    }
+    return;
+  }
+
+  var STATUS_PENDING = 0;
+  var STATUS_RESOLVED = 1;
+  var STATUS_REJECTED = 2;
+
+  // In an attempt to avoid silent exceptions, unhandled rejections are
+  // tracked and if they aren't handled in a certain amount of time an
+  // error is logged.
+  var REJECTION_TIMEOUT = 500;
+
+  var HandlerManager = {
+    handlers: [],
+    running: false,
+    unhandledRejections: [],
+    pendingRejectionCheck: false,
+
+    scheduleHandlers: function scheduleHandlers(promise) {
+      if (promise._status === STATUS_PENDING) {
+        return;
+      }
+
+      this.handlers = this.handlers.concat(promise._handlers);
+      promise._handlers = [];
+
+      if (this.running) {
+        return;
+      }
+      this.running = true;
+
+      setTimeout(this.runHandlers.bind(this), 0);
+    },
+
+    runHandlers: function runHandlers() {
+      var RUN_TIMEOUT = 1; // ms
+      var timeoutAt = Date.now() + RUN_TIMEOUT;
+      while (this.handlers.length > 0) {
+        var handler = this.handlers.shift();
+
+        var nextStatus = handler.thisPromise._status;
+        var nextValue = handler.thisPromise._value;
+
+        try {
+          if (nextStatus === STATUS_RESOLVED) {
+            if (typeof handler.onResolve === 'function') {
+              nextValue = handler.onResolve(nextValue);
+            }
+          } else if (typeof handler.onReject === 'function') {
+              nextValue = handler.onReject(nextValue);
+              nextStatus = STATUS_RESOLVED;
+
+              if (handler.thisPromise._unhandledRejection) {
+                this.removeUnhandeledRejection(handler.thisPromise);
+              }
+          }
+        } catch (ex) {
+          nextStatus = STATUS_REJECTED;
+          nextValue = ex;
+        }
+
+        handler.nextPromise._updateStatus(nextStatus, nextValue);
+        if (Date.now() >= timeoutAt) {
+          break;
+        }
+      }
+
+      if (this.handlers.length > 0) {
+        setTimeout(this.runHandlers.bind(this), 0);
+        return;
+      }
+
+      this.running = false;
+    },
+
+    addUnhandledRejection: function addUnhandledRejection(promise) {
+      this.unhandledRejections.push({
+        promise: promise,
+        time: Date.now()
+      });
+      this.scheduleRejectionCheck();
+    },
+
+    removeUnhandeledRejection: function removeUnhandeledRejection(promise) {
+      promise._unhandledRejection = false;
+      for (var i = 0; i < this.unhandledRejections.length; i++) {
+        if (this.unhandledRejections[i].promise === promise) {
+          this.unhandledRejections.splice(i);
+          i--;
+        }
+      }
+    },
+
+    scheduleRejectionCheck: function scheduleRejectionCheck() {
+      if (this.pendingRejectionCheck) {
+        return;
+      }
+      this.pendingRejectionCheck = true;
+      setTimeout(function rejectionCheck() {
+        this.pendingRejectionCheck = false;
+        var now = Date.now();
+        for (var i = 0; i < this.unhandledRejections.length; i++) {
+          if (now - this.unhandledRejections[i].time > REJECTION_TIMEOUT) {
+            var unhandled = this.unhandledRejections[i].promise._value;
+            var msg = 'Unhandled rejection: ' + unhandled;
+            if (unhandled.stack) {
+              msg += '\n' + unhandled.stack;
+            }
+            // Raising and catching the error, so debugger can break on it.
+            try {
+              throw new Error(msg);
+            } catch (_) {
+              console.warn(msg);
+            }
+            this.unhandledRejections.splice(i);
+            i--;
+          }
+        }
+        if (this.unhandledRejections.length) {
+          this.scheduleRejectionCheck();
+        }
+      }.bind(this), REJECTION_TIMEOUT);
+    }
+  };
+
+  var Promise = function Promise(resolver) {
+    this._status = STATUS_PENDING;
+    this._handlers = [];
+    try {
+      resolver.call(this, this._resolve.bind(this), this._reject.bind(this));
+    } catch (e) {
+      this._reject(e);
+    }
+  };
+
+  /**
+   * Builds a promise that is resolved when all the passed in promises are
+   * resolved.
+   * @param {array} promises array of data and/or promises to wait for.
+   * @return {Promise} New dependent promise.
+   */
+  Promise.all = function Promise_all(promises) {
+    var resolveAll, rejectAll;
+    var deferred = new Promise(function (resolve, reject) {
+      resolveAll = resolve;
+      rejectAll = reject;
+    });
+    var unresolved = promises.length;
+    var results = [];
+    if (unresolved === 0) {
+      resolveAll(results);
+      return deferred;
+    }
+    function reject(reason) {
+      if (deferred._status === STATUS_REJECTED) {
+        return;
+      }
+      results = [];
+      rejectAll(reason);
+    }
+    for (var i = 0, ii = promises.length; i < ii; ++i) {
+      var promise = promises[i];
+      var resolve = (function(i) {
+        return function(value) {
+          if (deferred._status === STATUS_REJECTED) {
+            return;
+          }
+          results[i] = value;
+          unresolved--;
+          if (unresolved === 0) {
+            resolveAll(results);
+          }
+        };
+      })(i);
+      if (Promise.isPromise(promise)) {
+        promise.then(resolve, reject);
+      } else {
+        resolve(promise);
+      }
+    }
+    return deferred;
+  };
+
+  /**
+   * Checks if the value is likely a promise (has a 'then' function).
+   * @return {boolean} true if value is thenable
+   */
+  Promise.isPromise = function Promise_isPromise(value) {
+    return value && typeof value.then === 'function';
+  };
+
+  /**
+   * Creates resolved promise
+   * @param value resolve value
+   * @returns {Promise}
+   */
+  Promise.resolve = function Promise_resolve(value) {
+    return new Promise(function (resolve) {
+      resolve(value);
+    });
+  };
+
+  /**
+   * Creates rejected promise
+   * @param reason rejection value
+   * @returns {Promise}
+   */
+  Promise.reject = function Promise_reject(reason) {
+    return new Promise(function (resolve, reject) {
+      reject(reason);
+    });
+  };
+
+  Promise.prototype = {
+    _status: null,
+    _value: null,
+    _handlers: null,
+    _unhandledRejection: null,
+
+    _updateStatus: function Promise__updateStatus(status, value) {
+      if (this._status === STATUS_RESOLVED ||
+          this._status === STATUS_REJECTED) {
+        return;
+      }
+
+      if (status === STATUS_RESOLVED &&
+          Promise.isPromise(value)) {
+        value.then(this._updateStatus.bind(this, STATUS_RESOLVED),
+                   this._updateStatus.bind(this, STATUS_REJECTED));
+        return;
+      }
+
+      this._status = status;
+      this._value = value;
+
+      if (status === STATUS_REJECTED && this._handlers.length === 0) {
+        this._unhandledRejection = true;
+        HandlerManager.addUnhandledRejection(this);
+      }
+
+      HandlerManager.scheduleHandlers(this);
+    },
+
+    _resolve: function Promise_resolve(value) {
+      this._updateStatus(STATUS_RESOLVED, value);
+    },
+
+    _reject: function Promise_reject(reason) {
+      this._updateStatus(STATUS_REJECTED, reason);
+    },
+
+    then: function Promise_then(onResolve, onReject) {
+      var nextPromise = new Promise(function (resolve, reject) {
+        this.resolve = resolve;
+        this.reject = reject;
+      });
+      this._handlers.push({
+        thisPromise: this,
+        onResolve: onResolve,
+        onReject: onReject,
+        nextPromise: nextPromise
+      });
+      HandlerManager.scheduleHandlers(this);
+      return nextPromise;
+    },
+
+    catch: function Promise_catch(onReject) {
+      return this.then(undefined, onReject);
+    }
+  };
+
+  globalScope.Promise = Promise;
+})();
+
+(function checkWeakMap() {
+  if (globalScope.WeakMap) {
+    return;
+  }
+
+  var id = 0;
+  function WeakMap() {
+    this.id = '$weakmap' + (id++);
+  }
+  WeakMap.prototype = {
+    has: function(obj) {
+      return !!Object.getOwnPropertyDescriptor(obj, this.id);
+    },
+    get: function(obj, defaultValue) {
+      return this.has(obj) ? obj[this.id] : defaultValue;
+    },
+    set: function(obj, value) {
+      Object.defineProperty(obj, this.id, {
+        value: value,
+        enumerable: false,
+        configurable: true
+      });
+    },
+    delete: function(obj) {
+      delete obj[this.id];
+    }
+  };
+
+  globalScope.WeakMap = WeakMap;
+})();
+
+// Polyfill from https://github.com/Polymer/URL
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+(function checkURLConstructor() {
+  // feature detect for URL constructor
+  var hasWorkingUrl = false;
+  try {
+    if (typeof URL === 'function' &&
+        typeof URL.prototype === 'object' &&
+        ('origin' in URL.prototype)) {
+      var u = new URL('b', 'http://a');
+      u.pathname = 'c%20d';
+      hasWorkingUrl = u.href === 'http://a/c%20d';
+    }
+  } catch (e) { }
+
+  if (hasWorkingUrl) {
+    return;
+  }
+
+  var relative = Object.create(null);
+  relative['ftp'] = 21;
+  relative['file'] = 0;
+  relative['gopher'] = 70;
+  relative['http'] = 80;
+  relative['https'] = 443;
+  relative['ws'] = 80;
+  relative['wss'] = 443;
+
+  var relativePathDotMapping = Object.create(null);
+  relativePathDotMapping['%2e'] = '.';
+  relativePathDotMapping['.%2e'] = '..';
+  relativePathDotMapping['%2e.'] = '..';
+  relativePathDotMapping['%2e%2e'] = '..';
+
+  function isRelativeScheme(scheme) {
+    return relative[scheme] !== undefined;
+  }
+
+  function invalid() {
+    clear.call(this);
+    this._isInvalid = true;
+  }
+
+  function IDNAToASCII(h) {
+    if (h === '') {
+      invalid.call(this);
+    }
+    // XXX
+    return h.toLowerCase();
+  }
+
+  function percentEscape(c) {
+    var unicode = c.charCodeAt(0);
+    if (unicode > 0x20 &&
+       unicode < 0x7F &&
+       // " # < > ? `
+       [0x22, 0x23, 0x3C, 0x3E, 0x3F, 0x60].indexOf(unicode) === -1
+      ) {
+      return c;
+    }
+    return encodeURIComponent(c);
+  }
+
+  function percentEscapeQuery(c) {
+    // XXX This actually needs to encode c using encoding and then
+    // convert the bytes one-by-one.
+
+    var unicode = c.charCodeAt(0);
+    if (unicode > 0x20 &&
+       unicode < 0x7F &&
+       // " # < > ` (do not escape '?')
+       [0x22, 0x23, 0x3C, 0x3E, 0x60].indexOf(unicode) === -1
+      ) {
+      return c;
+    }
+    return encodeURIComponent(c);
+  }
+
+  var EOF, ALPHA = /[a-zA-Z]/,
+      ALPHANUMERIC = /[a-zA-Z0-9\+\-\.]/;
+
+  function parse(input, stateOverride, base) {
+    function err(message) {
+      errors.push(message);
+    }
+
+    var state = stateOverride || 'scheme start',
+        cursor = 0,
+        buffer = '',
+        seenAt = false,
+        seenBracket = false,
+        errors = [];
+
+    loop: while ((input[cursor - 1] !== EOF || cursor === 0) &&
+                 !this._isInvalid) {
+      var c = input[cursor];
+      switch (state) {
+        case 'scheme start':
+          if (c && ALPHA.test(c)) {
+            buffer += c.toLowerCase(); // ASCII-safe
+            state = 'scheme';
+          } else if (!stateOverride) {
+            buffer = '';
+            state = 'no scheme';
+            continue;
+          } else {
+            err('Invalid scheme.');
+            break loop;
+          }
+          break;
+
+        case 'scheme':
+          if (c && ALPHANUMERIC.test(c)) {
+            buffer += c.toLowerCase(); // ASCII-safe
+          } else if (c === ':') {
+            this._scheme = buffer;
+            buffer = '';
+            if (stateOverride) {
+              break loop;
+            }
+            if (isRelativeScheme(this._scheme)) {
+              this._isRelative = true;
+            }
+            if (this._scheme === 'file') {
+              state = 'relative';
+            } else if (this._isRelative && base &&
+                       base._scheme === this._scheme) {
+              state = 'relative or authority';
+            } else if (this._isRelative) {
+              state = 'authority first slash';
+            } else {
+              state = 'scheme data';
+            }
+          } else if (!stateOverride) {
+            buffer = '';
+            cursor = 0;
+            state = 'no scheme';
+            continue;
+          } else if (EOF === c) {
+            break loop;
+          } else {
+            err('Code point not allowed in scheme: ' + c);
+            break loop;
+          }
+          break;
+
+        case 'scheme data':
+          if (c === '?') {
+            this._query = '?';
+            state = 'query';
+          } else if (c === '#') {
+            this._fragment = '#';
+            state = 'fragment';
+          } else {
+            // XXX error handling
+            if (EOF !== c && '\t' !== c && '\n' !== c && '\r' !== c) {
+              this._schemeData += percentEscape(c);
+            }
+          }
+          break;
+
+        case 'no scheme':
+          if (!base || !(isRelativeScheme(base._scheme))) {
+            err('Missing scheme.');
+            invalid.call(this);
+          } else {
+            state = 'relative';
+            continue;
+          }
+          break;
+
+        case 'relative or authority':
+          if (c === '/' && input[cursor + 1] === '/') {
+            state = 'authority ignore slashes';
+          } else {
+            err('Expected /, got: ' + c);
+            state = 'relative';
+            continue;
+          }
+          break;
+
+        case 'relative':
+          this._isRelative = true;
+          if ('file' !== this._scheme) {
+            this._scheme = base._scheme;
+          }
+          if (EOF === c) {
+            this._host = base._host;
+            this._port = base._port;
+            this._path = base._path.slice();
+            this._query = base._query;
+            this._username = base._username;
+            this._password = base._password;
+            break loop;
+          } else if (c === '/' || c === '\\') {
+            if (c === '\\') {
+              err('\\ is an invalid code point.');
+            }
+            state = 'relative slash';
+          } else if (c === '?') {
+            this._host = base._host;
+            this._port = base._port;
+            this._path = base._path.slice();
+            this._query = '?';
+            this._username = base._username;
+            this._password = base._password;
+            state = 'query';
+          } else if (c === '#') {
+            this._host = base._host;
+            this._port = base._port;
+            this._path = base._path.slice();
+            this._query = base._query;
+            this._fragment = '#';
+            this._username = base._username;
+            this._password = base._password;
+            state = 'fragment';
+          } else {
+            var nextC = input[cursor + 1];
+            var nextNextC = input[cursor + 2];
+            if ('file' !== this._scheme || !ALPHA.test(c) ||
+                (nextC !== ':' && nextC !== '|') ||
+                (EOF !== nextNextC && '/' !== nextNextC && '\\' !== nextNextC &&
+                '?' !== nextNextC && '#' !== nextNextC)) {
+              this._host = base._host;
+              this._port = base._port;
+              this._username = base._username;
+              this._password = base._password;
+              this._path = base._path.slice();
+              this._path.pop();
+            }
+            state = 'relative path';
+            continue;
+          }
+          break;
+
+        case 'relative slash':
+          if (c === '/' || c === '\\') {
+            if (c === '\\') {
+              err('\\ is an invalid code point.');
+            }
+            if (this._scheme === 'file') {
+              state = 'file host';
+            } else {
+              state = 'authority ignore slashes';
+            }
+          } else {
+            if ('file' !== this._scheme) {
+              this._host = base._host;
+              this._port = base._port;
+              this._username = base._username;
+              this._password = base._password;
+            }
+            state = 'relative path';
+            continue;
+          }
+          break;
+
+        case 'authority first slash':
+          if (c === '/') {
+            state = 'authority second slash';
+          } else {
+            err('Expected \'/\', got: ' + c);
+            state = 'authority ignore slashes';
+            continue;
+          }
+          break;
+
+        case 'authority second slash':
+          state = 'authority ignore slashes';
+          if ('/' !== c) {
+            err('Expected \'/\', got: ' + c);
+            continue;
+          }
+          break;
+
+        case 'authority ignore slashes':
+          if ('/' !== c && '\\' !== c) {
+            state = 'authority';
+            continue;
+          } else {
+            err('Expected authority, got: ' + c);
+          }
+          break;
+
+        case 'authority':
+          if (c === '@') {
+            if (seenAt) {
+              err('@ already seen.');
+              buffer += '%40';
+            }
+            seenAt = true;
+            for (var i = 0; i < buffer.length; i++) {
+              var cp = buffer[i];
+              if (cp === '\t' || cp === '\n' || cp === '\r') {
+                err('Invalid whitespace in authority.');
+                continue;
+              }
+              // XXX check URL code points
+              if (cp === ':' && this._password === null) {
+                this._password = '';
+                continue;
+              }
+              var tempC = percentEscape(cp);
+              if (null !== this._password) {
+                this._password += tempC;
+              } else {
+                this._username += tempC;
+              }
+            }
+            buffer = '';
+          } else if (c === EOF || c === '/' || c === '\\' ||
+                     c === '?' || c === '#') {
+            cursor -= buffer.length;
+            buffer = '';
+            state = 'host';
+            continue;
+          } else {
+            buffer += c;
+          }
+          break;
+
+        case 'file host':
+          if (c === EOF || c === '/' || c === '\\' || c === '?' || c === '#') {
+            if (buffer.length === 2 && ALPHA.test(buffer[0]) &&
+                (buffer[1] === ':' || buffer[1] === '|')) {
+              state = 'relative path';
+            } else if (buffer.length === 0) {
+              state = 'relative path start';
+            } else {
+              this._host = IDNAToASCII.call(this, buffer);
+              buffer = '';
+              state = 'relative path start';
+            }
+            continue;
+          } else if (c === '\t' || c === '\n' || c === '\r') {
+            err('Invalid whitespace in file host.');
+          } else {
+            buffer += c;
+          }
+          break;
+
+        case 'host':
+        case 'hostname':
+          if (c === ':' && !seenBracket) {
+            // XXX host parsing
+            this._host = IDNAToASCII.call(this, buffer);
+            buffer = '';
+            state = 'port';
+            if (stateOverride === 'hostname') {
+              break loop;
+            }
+          } else if (c === EOF || c === '/' ||
+                     c === '\\' || c === '?' || c === '#') {
+            this._host = IDNAToASCII.call(this, buffer);
+            buffer = '';
+            state = 'relative path start';
+            if (stateOverride) {
+              break loop;
+            }
+            continue;
+          } else if ('\t' !== c && '\n' !== c && '\r' !== c) {
+            if (c === '[') {
+              seenBracket = true;
+            } else if (c === ']') {
+              seenBracket = false;
+            }
+            buffer += c;
+          } else {
+            err('Invalid code point in host/hostname: ' + c);
+          }
+          break;
+
+        case 'port':
+          if (/[0-9]/.test(c)) {
+            buffer += c;
+          } else if (c === EOF || c === '/' || c === '\\' ||
+                     c === '?' || c === '#' || stateOverride) {
+            if ('' !== buffer) {
+              var temp = parseInt(buffer, 10);
+              if (temp !== relative[this._scheme]) {
+                this._port = temp + '';
+              }
+              buffer = '';
+            }
+            if (stateOverride) {
+              break loop;
+            }
+            state = 'relative path start';
+            continue;
+          } else if (c === '\t' || c === '\n' || c === '\r') {
+            err('Invalid code point in port: ' + c);
+          } else {
+            invalid.call(this);
+          }
+          break;
+
+        case 'relative path start':
+          if (c === '\\') {
+            err('\'\\\' not allowed in path.');
+          }
+          state = 'relative path';
+          if ('/' !== c && '\\' !== c) {
+            continue;
+          }
+          break;
+
+        case 'relative path':
+          if (c === EOF || c === '/' || c === '\\' ||
+              (!stateOverride && (c === '?' || c === '#'))) {
+            if (c === '\\') {
+              err('\\ not allowed in relative path.');
+            }
+            var tmp;
+            if ((tmp = relativePathDotMapping[buffer.toLowerCase()])) {
+              buffer = tmp;
+            }
+            if (buffer === '..') {
+              this._path.pop();
+              if ('/' !== c && '\\' !== c) {
+                this._path.push('');
+              }
+            } else if (buffer === '.' && '/' !== c && '\\' !== c) {
+              this._path.push('');
+            } else if ('.' !== buffer) {
+              if (this._scheme === 'file' && this._path.length === 0 &&
+                  buffer.length === 2 && ALPHA.test(buffer[0]) &&
+                  buffer[1] === '|') {
+                buffer = buffer[0] + ':';
+              }
+              this._path.push(buffer);
+            }
+            buffer = '';
+            if (c === '?') {
+              this._query = '?';
+              state = 'query';
+            } else if (c === '#') {
+              this._fragment = '#';
+              state = 'fragment';
+            }
+          } else if ('\t' !== c && '\n' !== c && '\r' !== c) {
+            buffer += percentEscape(c);
+          }
+          break;
+
+        case 'query':
+          if (!stateOverride && c === '#') {
+            this._fragment = '#';
+            state = 'fragment';
+          } else if (EOF !== c && '\t' !== c && '\n' !== c && '\r' !== c) {
+            this._query += percentEscapeQuery(c);
+          }
+          break;
+
+        case 'fragment':
+          if (EOF !== c && '\t' !== c && '\n' !== c && '\r' !== c) {
+            this._fragment += c;
+          }
+          break;
+      }
+
+      cursor++;
+    }
+  }
+
+  function clear() {
+    this._scheme = '';
+    this._schemeData = '';
+    this._username = '';
+    this._password = null;
+    this._host = '';
+    this._port = '';
+    this._path = [];
+    this._query = '';
+    this._fragment = '';
+    this._isInvalid = false;
+    this._isRelative = false;
+  }
+
+  // Does not process domain names or IP addresses.
+  // Does not handle encoding for the query parameter.
+  function JURL(url, base /* , encoding */) {
+    if (base !== undefined && !(base instanceof JURL)) {
+      base = new JURL(String(base));
+    }
+
+    this._url = url;
+    clear.call(this);
+
+    var input = url.replace(/^[ \t\r\n\f]+|[ \t\r\n\f]+$/g, '');
+    // encoding = encoding || 'utf-8'
+
+    parse.call(this, input, null, base);
+  }
+
+  JURL.prototype = {
+    toString: function() {
+      return this.href;
+    },
+    get href() {
+      if (this._isInvalid) {
+        return this._url;
+      }
+      var authority = '';
+      if ('' !== this._username || null !== this._password) {
+        authority = this._username +
+            (null !== this._password ? ':' + this._password : '') + '@';
+      }
+
+      return this.protocol +
+          (this._isRelative ? '//' + authority + this.host : '') +
+          this.pathname + this._query + this._fragment;
+    },
+    set href(href) {
+      clear.call(this);
+      parse.call(this, href);
+    },
+
+    get protocol() {
+      return this._scheme + ':';
+    },
+    set protocol(protocol) {
+      if (this._isInvalid) {
+        return;
+      }
+      parse.call(this, protocol + ':', 'scheme start');
+    },
+
+    get host() {
+      return this._isInvalid ? '' : this._port ?
+          this._host + ':' + this._port : this._host;
+    },
+    set host(host) {
+      if (this._isInvalid || !this._isRelative) {
+        return;
+      }
+      parse.call(this, host, 'host');
+    },
+
+    get hostname() {
+      return this._host;
+    },
+    set hostname(hostname) {
+      if (this._isInvalid || !this._isRelative) {
+        return;
+      }
+      parse.call(this, hostname, 'hostname');
+    },
+
+    get port() {
+      return this._port;
+    },
+    set port(port) {
+      if (this._isInvalid || !this._isRelative) {
+        return;
+      }
+      parse.call(this, port, 'port');
+    },
+
+    get pathname() {
+      return this._isInvalid ? '' : this._isRelative ?
+          '/' + this._path.join('/') : this._schemeData;
+    },
+    set pathname(pathname) {
+      if (this._isInvalid || !this._isRelative) {
+        return;
+      }
+      this._path = [];
+      parse.call(this, pathname, 'relative path start');
+    },
+
+    get search() {
+      return this._isInvalid || !this._query || this._query === '?' ?
+          '' : this._query;
+    },
+    set search(search) {
+      if (this._isInvalid || !this._isRelative) {
+        return;
+      }
+      this._query = '?';
+      if (search[0] === '?') {
+        search = search.slice(1);
+      }
+      parse.call(this, search, 'query');
+    },
+
+    get hash() {
+      return this._isInvalid || !this._fragment || this._fragment === '#' ?
+          '' : this._fragment;
+    },
+    set hash(hash) {
+      if (this._isInvalid) {
+        return;
+      }
+      this._fragment = '#';
+      if (hash[0] === '#') {
+        hash = hash.slice(1);
+      }
+      parse.call(this, hash, 'fragment');
+    },
+
+    get origin() {
+      var host;
+      if (this._isInvalid || !this._scheme) {
+        return '';
+      }
+      // javascript: Gecko returns String(""), WebKit/Blink String("null")
+      // Gecko throws error for "data://"
+      // data: Gecko returns "", Blink returns "data://", WebKit returns "null"
+      // Gecko returns String("") for file: mailto:
+      // WebKit/Blink returns String("SCHEME://") for file: mailto:
+      switch (this._scheme) {
+        case 'data':
+        case 'file':
+        case 'javascript':
+        case 'mailto':
+          return 'null';
+      }
+      host = this.host;
+      if (!host) {
+        return '';
+      }
+      return this._scheme + '://' + host;
+    }
+  };
+
+  // Copy over the static methods
+  var OriginalURL = globalScope.URL;
+  if (OriginalURL) {
+    JURL.createObjectURL = function(blob) {
+      // IE extension allows a second optional options argument.
+      // http://msdn.microsoft.com/en-us/library/ie/hh772302(v=vs.85).aspx
+      return OriginalURL.createObjectURL.apply(OriginalURL, arguments);
+    };
+    JURL.revokeObjectURL = function(url) {
+      OriginalURL.revokeObjectURL(url);
+    };
+  }
+
+  globalScope.URL = JURL;
+})();
+
+}
+
+}));
diff --git a/src/shared/util.js b/src/shared/util.js
index 4bd479a42..cdfd5980c 100644
--- a/src/shared/util.js
+++ b/src/shared/util.js
@@ -18,13 +18,14 @@
 
 (function (root, factory) {
   if (typeof define === 'function' && define.amd) {
-    define('pdfjs/shared/util', ['exports'], factory);
+    define('pdfjs/shared/util', ['exports', 'pdfjs/shared/compatibility'],
+      factory);
   } else if (typeof exports !== 'undefined') {
-    factory(exports);
+    factory(exports, require('./compatibility.js'));
   } else {
-    factory((root.pdfjsSharedUtil = {}));
+    factory((root.pdfjsSharedUtil = {}), root.pdfjsSharedCompatibility);
   }
-}(this, function (exports) {
+}(this, function (exports, compatibility) {
 
 var globalScope = (typeof window !== 'undefined') ? window :
                   (typeof global !== 'undefined') ? global :
@@ -1169,368 +1170,6 @@ function createPromiseCapability() {
   return capability;
 }
 
-/**
- * Polyfill for Promises:
- * The following promise implementation tries to generally implement the
- * Promise/A+ spec. Some notable differences from other promise libraries are:
- * - There currently isn't a separate deferred and promise object.
- * - Unhandled rejections eventually show an error if they aren't handled.
- *
- * Based off of the work in:
- * https://bugzilla.mozilla.org/show_bug.cgi?id=810490
- */
-(function PromiseClosure() {
-  if (globalScope.Promise) {
-    // Promises existing in the DOM/Worker, checking presence of all/resolve
-    if (typeof globalScope.Promise.all !== 'function') {
-      globalScope.Promise.all = function (iterable) {
-        var count = 0, results = [], resolve, reject;
-        var promise = new globalScope.Promise(function (resolve_, reject_) {
-          resolve = resolve_;
-          reject = reject_;
-        });
-        iterable.forEach(function (p, i) {
-          count++;
-          p.then(function (result) {
-            results[i] = result;
-            count--;
-            if (count === 0) {
-              resolve(results);
-            }
-          }, reject);
-        });
-        if (count === 0) {
-          resolve(results);
-        }
-        return promise;
-      };
-    }
-    if (typeof globalScope.Promise.resolve !== 'function') {
-      globalScope.Promise.resolve = function (value) {
-        return new globalScope.Promise(function (resolve) {
-          resolve(value);
-        });
-      };
-    }
-    if (typeof globalScope.Promise.reject !== 'function') {
-      globalScope.Promise.reject = function (reason) {
-        return new globalScope.Promise(function (resolve, reject) {
-          reject(reason);
-        });
-      };
-    }
-    if (typeof globalScope.Promise.prototype.catch !== 'function') {
-      globalScope.Promise.prototype.catch = function (onReject) {
-        return globalScope.Promise.prototype.then(undefined, onReject);
-      };
-    }
-    return;
-  }
-
-if (typeof PDFJSDev === 'undefined' || !PDFJSDev.test('MOZCENTRAL')) {
-  var STATUS_PENDING = 0;
-  var STATUS_RESOLVED = 1;
-  var STATUS_REJECTED = 2;
-
-  // In an attempt to avoid silent exceptions, unhandled rejections are
-  // tracked and if they aren't handled in a certain amount of time an
-  // error is logged.
-  var REJECTION_TIMEOUT = 500;
-
-  var HandlerManager = {
-    handlers: [],
-    running: false,
-    unhandledRejections: [],
-    pendingRejectionCheck: false,
-
-    scheduleHandlers: function scheduleHandlers(promise) {
-      if (promise._status === STATUS_PENDING) {
-        return;
-      }
-
-      this.handlers = this.handlers.concat(promise._handlers);
-      promise._handlers = [];
-
-      if (this.running) {
-        return;
-      }
-      this.running = true;
-
-      setTimeout(this.runHandlers.bind(this), 0);
-    },
-
-    runHandlers: function runHandlers() {
-      var RUN_TIMEOUT = 1; // ms
-      var timeoutAt = Date.now() + RUN_TIMEOUT;
-      while (this.handlers.length > 0) {
-        var handler = this.handlers.shift();
-
-        var nextStatus = handler.thisPromise._status;
-        var nextValue = handler.thisPromise._value;
-
-        try {
-          if (nextStatus === STATUS_RESOLVED) {
-            if (typeof handler.onResolve === 'function') {
-              nextValue = handler.onResolve(nextValue);
-            }
-          } else if (typeof handler.onReject === 'function') {
-              nextValue = handler.onReject(nextValue);
-              nextStatus = STATUS_RESOLVED;
-
-              if (handler.thisPromise._unhandledRejection) {
-                this.removeUnhandeledRejection(handler.thisPromise);
-              }
-          }
-        } catch (ex) {
-          nextStatus = STATUS_REJECTED;
-          nextValue = ex;
-        }
-
-        handler.nextPromise._updateStatus(nextStatus, nextValue);
-        if (Date.now() >= timeoutAt) {
-          break;
-        }
-      }
-
-      if (this.handlers.length > 0) {
-        setTimeout(this.runHandlers.bind(this), 0);
-        return;
-      }
-
-      this.running = false;
-    },
-
-    addUnhandledRejection: function addUnhandledRejection(promise) {
-      this.unhandledRejections.push({
-        promise: promise,
-        time: Date.now()
-      });
-      this.scheduleRejectionCheck();
-    },
-
-    removeUnhandeledRejection: function removeUnhandeledRejection(promise) {
-      promise._unhandledRejection = false;
-      for (var i = 0; i < this.unhandledRejections.length; i++) {
-        if (this.unhandledRejections[i].promise === promise) {
-          this.unhandledRejections.splice(i);
-          i--;
-        }
-      }
-    },
-
-    scheduleRejectionCheck: function scheduleRejectionCheck() {
-      if (this.pendingRejectionCheck) {
-        return;
-      }
-      this.pendingRejectionCheck = true;
-      setTimeout(function rejectionCheck() {
-        this.pendingRejectionCheck = false;
-        var now = Date.now();
-        for (var i = 0; i < this.unhandledRejections.length; i++) {
-          if (now - this.unhandledRejections[i].time > REJECTION_TIMEOUT) {
-            var unhandled = this.unhandledRejections[i].promise._value;
-            var msg = 'Unhandled rejection: ' + unhandled;
-            if (unhandled.stack) {
-              msg += '\n' + unhandled.stack;
-            }
-            warn(msg);
-            this.unhandledRejections.splice(i);
-            i--;
-          }
-        }
-        if (this.unhandledRejections.length) {
-          this.scheduleRejectionCheck();
-        }
-      }.bind(this), REJECTION_TIMEOUT);
-    }
-  };
-
-  var Promise = function Promise(resolver) {
-    this._status = STATUS_PENDING;
-    this._handlers = [];
-    try {
-      resolver.call(this, this._resolve.bind(this), this._reject.bind(this));
-    } catch (e) {
-      this._reject(e);
-    }
-  };
-
-  /**
-   * Builds a promise that is resolved when all the passed in promises are
-   * resolved.
-   * @param {array} promises array of data and/or promises to wait for.
-   * @return {Promise} New dependent promise.
-   */
-  Promise.all = function Promise_all(promises) {
-    var resolveAll, rejectAll;
-    var deferred = new Promise(function (resolve, reject) {
-      resolveAll = resolve;
-      rejectAll = reject;
-    });
-    var unresolved = promises.length;
-    var results = [];
-    if (unresolved === 0) {
-      resolveAll(results);
-      return deferred;
-    }
-    function reject(reason) {
-      if (deferred._status === STATUS_REJECTED) {
-        return;
-      }
-      results = [];
-      rejectAll(reason);
-    }
-    for (var i = 0, ii = promises.length; i < ii; ++i) {
-      var promise = promises[i];
-      var resolve = (function(i) {
-        return function(value) {
-          if (deferred._status === STATUS_REJECTED) {
-            return;
-          }
-          results[i] = value;
-          unresolved--;
-          if (unresolved === 0) {
-            resolveAll(results);
-          }
-        };
-      })(i);
-      if (Promise.isPromise(promise)) {
-        promise.then(resolve, reject);
-      } else {
-        resolve(promise);
-      }
-    }
-    return deferred;
-  };
-
-  /**
-   * Checks if the value is likely a promise (has a 'then' function).
-   * @return {boolean} true if value is thenable
-   */
-  Promise.isPromise = function Promise_isPromise(value) {
-    return value && typeof value.then === 'function';
-  };
-
-  /**
-   * Creates resolved promise
-   * @param value resolve value
-   * @returns {Promise}
-   */
-  Promise.resolve = function Promise_resolve(value) {
-    return new Promise(function (resolve) {
-      resolve(value);
-    });
-  };
-
-  /**
-   * Creates rejected promise
-   * @param reason rejection value
-   * @returns {Promise}
-   */
-  Promise.reject = function Promise_reject(reason) {
-    return new Promise(function (resolve, reject) {
-      reject(reason);
-    });
-  };
-
-  Promise.prototype = {
-    _status: null,
-    _value: null,
-    _handlers: null,
-    _unhandledRejection: null,
-
-    _updateStatus: function Promise__updateStatus(status, value) {
-      if (this._status === STATUS_RESOLVED ||
-          this._status === STATUS_REJECTED) {
-        return;
-      }
-
-      if (status === STATUS_RESOLVED &&
-          Promise.isPromise(value)) {
-        value.then(this._updateStatus.bind(this, STATUS_RESOLVED),
-                   this._updateStatus.bind(this, STATUS_REJECTED));
-        return;
-      }
-
-      this._status = status;
-      this._value = value;
-
-      if (status === STATUS_REJECTED && this._handlers.length === 0) {
-        this._unhandledRejection = true;
-        HandlerManager.addUnhandledRejection(this);
-      }
-
-      HandlerManager.scheduleHandlers(this);
-    },
-
-    _resolve: function Promise_resolve(value) {
-      this._updateStatus(STATUS_RESOLVED, value);
-    },
-
-    _reject: function Promise_reject(reason) {
-      this._updateStatus(STATUS_REJECTED, reason);
-    },
-
-    then: function Promise_then(onResolve, onReject) {
-      var nextPromise = new Promise(function (resolve, reject) {
-        this.resolve = resolve;
-        this.reject = reject;
-      });
-      this._handlers.push({
-        thisPromise: this,
-        onResolve: onResolve,
-        onReject: onReject,
-        nextPromise: nextPromise
-      });
-      HandlerManager.scheduleHandlers(this);
-      return nextPromise;
-    },
-
-    catch: function Promise_catch(onReject) {
-      return this.then(undefined, onReject);
-    }
-  };
-
-  globalScope.Promise = Promise;
-} else {
-  throw new Error('DOM Promise is not present');
-}
-
-})();
-
-if (typeof PDFJSDev === 'undefined' || !PDFJSDev.test('MOZCENTRAL')) {
-  (function WeakMapClosure() {
-    if (globalScope.WeakMap) {
-      return;
-    }
-
-    var id = 0;
-    function WeakMap() {
-      this.id = '$weakmap' + (id++);
-    }
-    WeakMap.prototype = {
-      has: function(obj) {
-        return !!Object.getOwnPropertyDescriptor(obj, this.id);
-      },
-      get: function(obj, defaultValue) {
-        return this.has(obj) ? obj[this.id] : defaultValue;
-      },
-      set: function(obj, value) {
-        Object.defineProperty(obj, this.id, {
-          value: value,
-          enumerable: false,
-          configurable: true
-        });
-      },
-      delete: function(obj) {
-        delete obj[this.id];
-      }
-    };
-
-    globalScope.WeakMap = WeakMap;
-  })();
-}
-
 var StatTimer = (function StatTimerClosure() {
   function rpad(str, pad, length) {
     while (str.length < length) {
@@ -1770,650 +1409,6 @@ function loadJpegStream(id, imageUrl, objs) {
   img.src = imageUrl;
 }
 
-if (typeof PDFJSDev === 'undefined' || !PDFJSDev.test('MOZCENTRAL')) {
-// Polyfill from https://github.com/Polymer/URL
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-(function checkURLConstructor(scope) {
-  // feature detect for URL constructor
-  var hasWorkingUrl = false;
-  try {
-    if (typeof URL === 'function' &&
-        typeof URL.prototype === 'object' &&
-        ('origin' in URL.prototype)) {
-      var u = new URL('b', 'http://a');
-      u.pathname = 'c%20d';
-      hasWorkingUrl = u.href === 'http://a/c%20d';
-    }
-  } catch (e) { }
-
-  if (hasWorkingUrl) {
-    return;
-  }
-
-  var relative = Object.create(null);
-  relative['ftp'] = 21;
-  relative['file'] = 0;
-  relative['gopher'] = 70;
-  relative['http'] = 80;
-  relative['https'] = 443;
-  relative['ws'] = 80;
-  relative['wss'] = 443;
-
-  var relativePathDotMapping = Object.create(null);
-  relativePathDotMapping['%2e'] = '.';
-  relativePathDotMapping['.%2e'] = '..';
-  relativePathDotMapping['%2e.'] = '..';
-  relativePathDotMapping['%2e%2e'] = '..';
-
-  function isRelativeScheme(scheme) {
-    return relative[scheme] !== undefined;
-  }
-
-  function invalid() {
-    clear.call(this);
-    this._isInvalid = true;
-  }
-
-  function IDNAToASCII(h) {
-    if (h === '') {
-      invalid.call(this);
-    }
-    // XXX
-    return h.toLowerCase();
-  }
-
-  function percentEscape(c) {
-    var unicode = c.charCodeAt(0);
-    if (unicode > 0x20 &&
-       unicode < 0x7F &&
-       // " # < > ? `
-       [0x22, 0x23, 0x3C, 0x3E, 0x3F, 0x60].indexOf(unicode) === -1
-      ) {
-      return c;
-    }
-    return encodeURIComponent(c);
-  }
-
-  function percentEscapeQuery(c) {
-    // XXX This actually needs to encode c using encoding and then
-    // convert the bytes one-by-one.
-
-    var unicode = c.charCodeAt(0);
-    if (unicode > 0x20 &&
-       unicode < 0x7F &&
-       // " # < > ` (do not escape '?')
-       [0x22, 0x23, 0x3C, 0x3E, 0x60].indexOf(unicode) === -1
-      ) {
-      return c;
-    }
-    return encodeURIComponent(c);
-  }
-
-  var EOF, ALPHA = /[a-zA-Z]/,
-      ALPHANUMERIC = /[a-zA-Z0-9\+\-\.]/;
-
-  function parse(input, stateOverride, base) {
-    function err(message) {
-      errors.push(message);
-    }
-
-    var state = stateOverride || 'scheme start',
-        cursor = 0,
-        buffer = '',
-        seenAt = false,
-        seenBracket = false,
-        errors = [];
-
-    loop: while ((input[cursor - 1] !== EOF || cursor === 0) &&
-                 !this._isInvalid) {
-      var c = input[cursor];
-      switch (state) {
-        case 'scheme start':
-          if (c && ALPHA.test(c)) {
-            buffer += c.toLowerCase(); // ASCII-safe
-            state = 'scheme';
-          } else if (!stateOverride) {
-            buffer = '';
-            state = 'no scheme';
-            continue;
-          } else {
-            err('Invalid scheme.');
-            break loop;
-          }
-          break;
-
-        case 'scheme':
-          if (c && ALPHANUMERIC.test(c)) {
-            buffer += c.toLowerCase(); // ASCII-safe
-          } else if (c === ':') {
-            this._scheme = buffer;
-            buffer = '';
-            if (stateOverride) {
-              break loop;
-            }
-            if (isRelativeScheme(this._scheme)) {
-              this._isRelative = true;
-            }
-            if (this._scheme === 'file') {
-              state = 'relative';
-            } else if (this._isRelative && base &&
-                       base._scheme === this._scheme) {
-              state = 'relative or authority';
-            } else if (this._isRelative) {
-              state = 'authority first slash';
-            } else {
-              state = 'scheme data';
-            }
-          } else if (!stateOverride) {
-            buffer = '';
-            cursor = 0;
-            state = 'no scheme';
-            continue;
-          } else if (EOF === c) {
-            break loop;
-          } else {
-            err('Code point not allowed in scheme: ' + c);
-            break loop;
-          }
-          break;
-
-        case 'scheme data':
-          if (c === '?') {
-            this._query = '?';
-            state = 'query';
-          } else if (c === '#') {
-            this._fragment = '#';
-            state = 'fragment';
-          } else {
-            // XXX error handling
-            if (EOF !== c && '\t' !== c && '\n' !== c && '\r' !== c) {
-              this._schemeData += percentEscape(c);
-            }
-          }
-          break;
-
-        case 'no scheme':
-          if (!base || !(isRelativeScheme(base._scheme))) {
-            err('Missing scheme.');
-            invalid.call(this);
-          } else {
-            state = 'relative';
-            continue;
-          }
-          break;
-
-        case 'relative or authority':
-          if (c === '/' && input[cursor + 1] === '/') {
-            state = 'authority ignore slashes';
-          } else {
-            err('Expected /, got: ' + c);
-            state = 'relative';
-            continue;
-          }
-          break;
-
-        case 'relative':
-          this._isRelative = true;
-          if ('file' !== this._scheme) {
-            this._scheme = base._scheme;
-          }
-          if (EOF === c) {
-            this._host = base._host;
-            this._port = base._port;
-            this._path = base._path.slice();
-            this._query = base._query;
-            this._username = base._username;
-            this._password = base._password;
-            break loop;
-          } else if (c === '/' || c === '\\') {
-            if (c === '\\') {
-              err('\\ is an invalid code point.');
-            }
-            state = 'relative slash';
-          } else if (c === '?') {
-            this._host = base._host;
-            this._port = base._port;
-            this._path = base._path.slice();
-            this._query = '?';
-            this._username = base._username;
-            this._password = base._password;
-            state = 'query';
-          } else if (c === '#') {
-            this._host = base._host;
-            this._port = base._port;
-            this._path = base._path.slice();
-            this._query = base._query;
-            this._fragment = '#';
-            this._username = base._username;
-            this._password = base._password;
-            state = 'fragment';
-          } else {
-            var nextC = input[cursor + 1];
-            var nextNextC = input[cursor + 2];
-            if ('file' !== this._scheme || !ALPHA.test(c) ||
-                (nextC !== ':' && nextC !== '|') ||
-                (EOF !== nextNextC && '/' !== nextNextC && '\\' !== nextNextC &&
-                '?' !== nextNextC && '#' !== nextNextC)) {
-              this._host = base._host;
-              this._port = base._port;
-              this._username = base._username;
-              this._password = base._password;
-              this._path = base._path.slice();
-              this._path.pop();
-            }
-            state = 'relative path';
-            continue;
-          }
-          break;
-
-        case 'relative slash':
-          if (c === '/' || c === '\\') {
-            if (c === '\\') {
-              err('\\ is an invalid code point.');
-            }
-            if (this._scheme === 'file') {
-              state = 'file host';
-            } else {
-              state = 'authority ignore slashes';
-            }
-          } else {
-            if ('file' !== this._scheme) {
-              this._host = base._host;
-              this._port = base._port;
-              this._username = base._username;
-              this._password = base._password;
-            }
-            state = 'relative path';
-            continue;
-          }
-          break;
-
-        case 'authority first slash':
-          if (c === '/') {
-            state = 'authority second slash';
-          } else {
-            err('Expected \'/\', got: ' + c);
-            state = 'authority ignore slashes';
-            continue;
-          }
-          break;
-
-        case 'authority second slash':
-          state = 'authority ignore slashes';
-          if ('/' !== c) {
-            err('Expected \'/\', got: ' + c);
-            continue;
-          }
-          break;
-
-        case 'authority ignore slashes':
-          if ('/' !== c && '\\' !== c) {
-            state = 'authority';
-            continue;
-          } else {
-            err('Expected authority, got: ' + c);
-          }
-          break;
-
-        case 'authority':
-          if (c === '@') {
-            if (seenAt) {
-              err('@ already seen.');
-              buffer += '%40';
-            }
-            seenAt = true;
-            for (var i = 0; i < buffer.length; i++) {
-              var cp = buffer[i];
-              if (cp === '\t' || cp === '\n' || cp === '\r') {
-                err('Invalid whitespace in authority.');
-                continue;
-              }
-              // XXX check URL code points
-              if (cp === ':' && this._password === null) {
-                this._password = '';
-                continue;
-              }
-              var tempC = percentEscape(cp);
-              if (null !== this._password) {
-                this._password += tempC;
-              } else {
-                this._username += tempC;
-              }
-            }
-            buffer = '';
-          } else if (c === EOF || c === '/' || c === '\\' ||
-                     c === '?' || c === '#') {
-            cursor -= buffer.length;
-            buffer = '';
-            state = 'host';
-            continue;
-          } else {
-            buffer += c;
-          }
-          break;
-
-        case 'file host':
-          if (c === EOF || c === '/' || c === '\\' || c === '?' || c === '#') {
-            if (buffer.length === 2 && ALPHA.test(buffer[0]) &&
-                (buffer[1] === ':' || buffer[1] === '|')) {
-              state = 'relative path';
-            } else if (buffer.length === 0) {
-              state = 'relative path start';
-            } else {
-              this._host = IDNAToASCII.call(this, buffer);
-              buffer = '';
-              state = 'relative path start';
-            }
-            continue;
-          } else if (c === '\t' || c === '\n' || c === '\r') {
-            err('Invalid whitespace in file host.');
-          } else {
-            buffer += c;
-          }
-          break;
-
-        case 'host':
-        case 'hostname':
-          if (c === ':' && !seenBracket) {
-            // XXX host parsing
-            this._host = IDNAToASCII.call(this, buffer);
-            buffer = '';
-            state = 'port';
-            if (stateOverride === 'hostname') {
-              break loop;
-            }
-          } else if (c === EOF || c === '/' ||
-                     c === '\\' || c === '?' || c === '#') {
-            this._host = IDNAToASCII.call(this, buffer);
-            buffer = '';
-            state = 'relative path start';
-            if (stateOverride) {
-              break loop;
-            }
-            continue;
-          } else if ('\t' !== c && '\n' !== c && '\r' !== c) {
-            if (c === '[') {
-              seenBracket = true;
-            } else if (c === ']') {
-              seenBracket = false;
-            }
-            buffer += c;
-          } else {
-            err('Invalid code point in host/hostname: ' + c);
-          }
-          break;
-
-        case 'port':
-          if (/[0-9]/.test(c)) {
-            buffer += c;
-          } else if (c === EOF || c === '/' || c === '\\' ||
-                     c === '?' || c === '#' || stateOverride) {
-            if ('' !== buffer) {
-              var temp = parseInt(buffer, 10);
-              if (temp !== relative[this._scheme]) {
-                this._port = temp + '';
-              }
-              buffer = '';
-            }
-            if (stateOverride) {
-              break loop;
-            }
-            state = 'relative path start';
-            continue;
-          } else if (c === '\t' || c === '\n' || c === '\r') {
-            err('Invalid code point in port: ' + c);
-          } else {
-            invalid.call(this);
-          }
-          break;
-
-        case 'relative path start':
-          if (c === '\\') {
-            err('\'\\\' not allowed in path.');
-          }
-          state = 'relative path';
-          if ('/' !== c && '\\' !== c) {
-            continue;
-          }
-          break;
-
-        case 'relative path':
-          if (c === EOF || c === '/' || c === '\\' ||
-              (!stateOverride && (c === '?' || c === '#'))) {
-            if (c === '\\') {
-              err('\\ not allowed in relative path.');
-            }
-            var tmp;
-            if ((tmp = relativePathDotMapping[buffer.toLowerCase()])) {
-              buffer = tmp;
-            }
-            if (buffer === '..') {
-              this._path.pop();
-              if ('/' !== c && '\\' !== c) {
-                this._path.push('');
-              }
-            } else if (buffer === '.' && '/' !== c && '\\' !== c) {
-              this._path.push('');
-            } else if ('.' !== buffer) {
-              if (this._scheme === 'file' && this._path.length === 0 &&
-                  buffer.length === 2 && ALPHA.test(buffer[0]) &&
-                  buffer[1] === '|') {
-                buffer = buffer[0] + ':';
-              }
-              this._path.push(buffer);
-            }
-            buffer = '';
-            if (c === '?') {
-              this._query = '?';
-              state = 'query';
-            } else if (c === '#') {
-              this._fragment = '#';
-              state = 'fragment';
-            }
-          } else if ('\t' !== c && '\n' !== c && '\r' !== c) {
-            buffer += percentEscape(c);
-          }
-          break;
-
-        case 'query':
-          if (!stateOverride && c === '#') {
-            this._fragment = '#';
-            state = 'fragment';
-          } else if (EOF !== c && '\t' !== c && '\n' !== c && '\r' !== c) {
-            this._query += percentEscapeQuery(c);
-          }
-          break;
-
-        case 'fragment':
-          if (EOF !== c && '\t' !== c && '\n' !== c && '\r' !== c) {
-            this._fragment += c;
-          }
-          break;
-      }
-
-      cursor++;
-    }
-  }
-
-  function clear() {
-    this._scheme = '';
-    this._schemeData = '';
-    this._username = '';
-    this._password = null;
-    this._host = '';
-    this._port = '';
-    this._path = [];
-    this._query = '';
-    this._fragment = '';
-    this._isInvalid = false;
-    this._isRelative = false;
-  }
-
-  // Does not process domain names or IP addresses.
-  // Does not handle encoding for the query parameter.
-  function JURL(url, base /* , encoding */) {
-    if (base !== undefined && !(base instanceof JURL)) {
-      base = new JURL(String(base));
-    }
-
-    this._url = url;
-    clear.call(this);
-
-    var input = url.replace(/^[ \t\r\n\f]+|[ \t\r\n\f]+$/g, '');
-    // encoding = encoding || 'utf-8'
-
-    parse.call(this, input, null, base);
-  }
-
-  JURL.prototype = {
-    toString: function() {
-      return this.href;
-    },
-    get href() {
-      if (this._isInvalid) {
-        return this._url;
-      }
-      var authority = '';
-      if ('' !== this._username || null !== this._password) {
-        authority = this._username +
-            (null !== this._password ? ':' + this._password : '') + '@';
-      }
-
-      return this.protocol +
-          (this._isRelative ? '//' + authority + this.host : '') +
-          this.pathname + this._query + this._fragment;
-    },
-    set href(href) {
-      clear.call(this);
-      parse.call(this, href);
-    },
-
-    get protocol() {
-      return this._scheme + ':';
-    },
-    set protocol(protocol) {
-      if (this._isInvalid) {
-        return;
-      }
-      parse.call(this, protocol + ':', 'scheme start');
-    },
-
-    get host() {
-      return this._isInvalid ? '' : this._port ?
-          this._host + ':' + this._port : this._host;
-    },
-    set host(host) {
-      if (this._isInvalid || !this._isRelative) {
-        return;
-      }
-      parse.call(this, host, 'host');
-    },
-
-    get hostname() {
-      return this._host;
-    },
-    set hostname(hostname) {
-      if (this._isInvalid || !this._isRelative) {
-        return;
-      }
-      parse.call(this, hostname, 'hostname');
-    },
-
-    get port() {
-      return this._port;
-    },
-    set port(port) {
-      if (this._isInvalid || !this._isRelative) {
-        return;
-      }
-      parse.call(this, port, 'port');
-    },
-
-    get pathname() {
-      return this._isInvalid ? '' : this._isRelative ?
-          '/' + this._path.join('/') : this._schemeData;
-    },
-    set pathname(pathname) {
-      if (this._isInvalid || !this._isRelative) {
-        return;
-      }
-      this._path = [];
-      parse.call(this, pathname, 'relative path start');
-    },
-
-    get search() {
-      return this._isInvalid || !this._query || this._query === '?' ?
-          '' : this._query;
-    },
-    set search(search) {
-      if (this._isInvalid || !this._isRelative) {
-        return;
-      }
-      this._query = '?';
-      if (search[0] === '?') {
-        search = search.slice(1);
-      }
-      parse.call(this, search, 'query');
-    },
-
-    get hash() {
-      return this._isInvalid || !this._fragment || this._fragment === '#' ?
-          '' : this._fragment;
-    },
-    set hash(hash) {
-      if (this._isInvalid) {
-        return;
-      }
-      this._fragment = '#';
-      if (hash[0] === '#') {
-        hash = hash.slice(1);
-      }
-      parse.call(this, hash, 'fragment');
-    },
-
-    get origin() {
-      var host;
-      if (this._isInvalid || !this._scheme) {
-        return '';
-      }
-      // javascript: Gecko returns String(""), WebKit/Blink String("null")
-      // Gecko throws error for "data://"
-      // data: Gecko returns "", Blink returns "data://", WebKit returns "null"
-      // Gecko returns String("") for file: mailto:
-      // WebKit/Blink returns String("SCHEME://") for file: mailto:
-      switch (this._scheme) {
-        case 'data':
-        case 'file':
-        case 'javascript':
-        case 'mailto':
-          return 'null';
-      }
-      host = this.host;
-      if (!host) {
-        return '';
-      }
-      return this._scheme + '://' + host;
-    }
-  };
-
-  // Copy over the static methods
-  var OriginalURL = scope.URL;
-  if (OriginalURL) {
-    JURL.createObjectURL = function(blob) {
-      // IE extension allows a second optional options argument.
-      // http://msdn.microsoft.com/en-us/library/ie/hh772302(v=vs.85).aspx
-      return OriginalURL.createObjectURL.apply(OriginalURL, arguments);
-    };
-    JURL.revokeObjectURL = function(url) {
-      OriginalURL.revokeObjectURL(url);
-    };
-  }
-
-  scope.URL = JURL;
-})(globalScope);
-}
-
 exports.FONT_IDENTITY_MATRIX = FONT_IDENTITY_MATRIX;
 exports.IDENTITY_MATRIX = IDENTITY_MATRIX;
 exports.OPS = OPS;
diff --git a/web/compatibility.js b/web/compatibility.js
index 4bed22d79..8621afe31 100644
--- a/web/compatibility.js
+++ b/web/compatibility.js
@@ -1,4 +1,4 @@
-/* Copyright 2012 Mozilla Foundation
+/* Copyright 2017 Mozilla Foundation
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -12,653 +12,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-/* eslint strict: ["error", "function"] */
-/* eslint-disable no-extend-native */
-/* globals VBArray, PDFJS */
+'use strict';
 
-(function compatibilityWrapper() {
-  'use strict';
-
-var userAgent = navigator.userAgent;
-
-var isAndroid = /Android/.test(userAgent);
-var isAndroidPre3 = /Android\s[0-2][^\d]/.test(userAgent);
-var isAndroidPre5 = /Android\s[0-4][^\d]/.test(userAgent);
-var isChrome = userAgent.indexOf('Chrom') >= 0;
-var isChromeWithRangeBug = /Chrome\/(39|40)\./.test(userAgent);
-var isIOSChrome = userAgent.indexOf('CriOS') >= 0;
-var isIE = userAgent.indexOf('Trident') >= 0;
-var isIOS = /\b(iPad|iPhone|iPod)(?=;)/.test(userAgent);
-var isOpera = userAgent.indexOf('Opera') >= 0;
-var isSafari = /Safari\//.test(userAgent) &&
-               !/(Chrome\/|Android\s)/.test(userAgent);
-
-// Initializing PDFJS global object here, it case if we need to change/disable
-// some PDF.js features, e.g. range requests
-if (typeof PDFJS === 'undefined') {
-  (typeof window !== 'undefined' ? window : this).PDFJS = {};
-}
-
-// Checking if the typed arrays are supported
-// Support: iOS<6.0 (subarray), IE<10, Android<4.0
-(function checkTypedArrayCompatibility() {
-  if (typeof Uint8Array !== 'undefined') {
-    // Support: iOS<6.0
-    if (typeof Uint8Array.prototype.subarray === 'undefined') {
-      Uint8Array.prototype.subarray = function subarray(start, end) {
-        return new Uint8Array(this.slice(start, end));
-      };
-      Float32Array.prototype.subarray = function subarray(start, end) {
-        return new Float32Array(this.slice(start, end));
-      };
-    }
-
-    // Support: Android<4.1
-    if (typeof Float64Array === 'undefined') {
-      window.Float64Array = Float32Array;
-    }
-    return;
-  }
-
-  function subarray(start, end) {
-    return new TypedArray(this.slice(start, end));
-  }
-
-  function setArrayOffset(array, offset) {
-    if (arguments.length < 2) {
-      offset = 0;
-    }
-    for (var i = 0, n = array.length; i < n; ++i, ++offset) {
-      this[offset] = array[i] & 0xFF;
-    }
-  }
-
-  function TypedArray(arg1) {
-    var result, i, n;
-    if (typeof arg1 === 'number') {
-      result = [];
-      for (i = 0; i < arg1; ++i) {
-        result[i] = 0;
-      }
-    } else if ('slice' in arg1) {
-      result = arg1.slice(0);
-    } else {
-      result = [];
-      for (i = 0, n = arg1.length; i < n; ++i) {
-        result[i] = arg1[i];
-      }
-    }
-
-    result.subarray = subarray;
-    result.buffer = result;
-    result.byteLength = result.length;
-    result.set = setArrayOffset;
-
-    if (typeof arg1 === 'object' && arg1.buffer) {
-      result.buffer = arg1.buffer;
-    }
-    return result;
-  }
-
-  window.Uint8Array = TypedArray;
-  window.Int8Array = TypedArray;
-
-  // we don't need support for set, byteLength for 32-bit array
-  // so we can use the TypedArray as well
-  window.Uint32Array = TypedArray;
-  window.Int32Array = TypedArray;
-  window.Uint16Array = TypedArray;
-  window.Float32Array = TypedArray;
-  window.Float64Array = TypedArray;
-})();
-
-// URL = URL || webkitURL
-// Support: Safari<7, Android 4.2+
-(function normalizeURLObject() {
-  if (!window.URL) {
-    window.URL = window.webkitURL;
-  }
-})();
-
-// Object.defineProperty()?
-// Support: Android<4.0, Safari<5.1
-(function checkObjectDefinePropertyCompatibility() {
-  if (typeof Object.defineProperty !== 'undefined') {
-    var definePropertyPossible = true;
-    try {
-      // some browsers (e.g. safari) cannot use defineProperty() on DOM objects
-      // and thus the native version is not sufficient
-      Object.defineProperty(new Image(), 'id', { value: 'test' });
-      // ... another test for android gb browser for non-DOM objects
-      var Test = function Test() {};
-      Test.prototype = { get id() { } };
-      Object.defineProperty(new Test(), 'id',
-        { value: '', configurable: true, enumerable: true, writable: false });
-    } catch (e) {
-      definePropertyPossible = false;
-    }
-    if (definePropertyPossible) {
-      return;
-    }
-  }
-
-  Object.defineProperty = function objectDefineProperty(obj, name, def) {
-    delete obj[name];
-    if ('get' in def) {
-      obj.__defineGetter__(name, def['get']);
-    }
-    if ('set' in def) {
-      obj.__defineSetter__(name, def['set']);
-    }
-    if ('value' in def) {
-      obj.__defineSetter__(name, function objectDefinePropertySetter(value) {
-        this.__defineGetter__(name, function objectDefinePropertyGetter() {
-          return value;
-        });
-        return value;
-      });
-      obj[name] = def.value;
-    }
-  };
-})();
-
-
-// No XMLHttpRequest#response?
-// Support: IE<11, Android <4.0
-(function checkXMLHttpRequestResponseCompatibility() {
-  var xhrPrototype = XMLHttpRequest.prototype;
-  var xhr = new XMLHttpRequest();
-  if (!('overrideMimeType' in xhr)) {
-    // IE10 might have response, but not overrideMimeType
-    // Support: IE10
-    Object.defineProperty(xhrPrototype, 'overrideMimeType', {
-      value: function xmlHttpRequestOverrideMimeType(mimeType) {}
-    });
-  }
-  if ('responseType' in xhr) {
-    return;
-  }
-
-  // The worker will be using XHR, so we can save time and disable worker.
-  PDFJS.disableWorker = true;
-
-  Object.defineProperty(xhrPrototype, 'responseType', {
-    get: function xmlHttpRequestGetResponseType() {
-      return this._responseType || 'text';
-    },
-    set: function xmlHttpRequestSetResponseType(value) {
-      if (value === 'text' || value === 'arraybuffer') {
-        this._responseType = value;
-        if (value === 'arraybuffer' &&
-            typeof this.overrideMimeType === 'function') {
-          this.overrideMimeType('text/plain; charset=x-user-defined');
-        }
-      }
-    }
-  });
-
-  // Support: IE9
-  if (typeof VBArray !== 'undefined') {
-    Object.defineProperty(xhrPrototype, 'response', {
-      get: function xmlHttpRequestResponseGet() {
-        if (this.responseType === 'arraybuffer') {
-          return new Uint8Array(new VBArray(this.responseBody).toArray());
-        }
-        return this.responseText;
-      }
-    });
-    return;
-  }
-
-  Object.defineProperty(xhrPrototype, 'response', {
-    get: function xmlHttpRequestResponseGet() {
-      if (this.responseType !== 'arraybuffer') {
-        return this.responseText;
-      }
-      var text = this.responseText;
-      var i, n = text.length;
-      var result = new Uint8Array(n);
-      for (i = 0; i < n; ++i) {
-        result[i] = text.charCodeAt(i) & 0xFF;
-      }
-      return result.buffer;
-    }
-  });
-})();
-
-// window.btoa (base64 encode function) ?
-// Support: IE<10
-(function checkWindowBtoaCompatibility() {
-  if ('btoa' in window) {
-    return;
-  }
-
-  var digits =
-    'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
-
-  window.btoa = function windowBtoa(chars) {
-    var buffer = '';
-    var i, n;
-    for (i = 0, n = chars.length; i < n; i += 3) {
-      var b1 = chars.charCodeAt(i) & 0xFF;
-      var b2 = chars.charCodeAt(i + 1) & 0xFF;
-      var b3 = chars.charCodeAt(i + 2) & 0xFF;
-      var d1 = b1 >> 2, d2 = ((b1 & 3) << 4) | (b2 >> 4);
-      var d3 = i + 1 < n ? ((b2 & 0xF) << 2) | (b3 >> 6) : 64;
-      var d4 = i + 2 < n ? (b3 & 0x3F) : 64;
-      buffer += (digits.charAt(d1) + digits.charAt(d2) +
-                 digits.charAt(d3) + digits.charAt(d4));
-    }
-    return buffer;
-  };
-})();
-
-// window.atob (base64 encode function)?
-// Support: IE<10
-(function checkWindowAtobCompatibility() {
-  if ('atob' in window) {
-    return;
-  }
-
-  // https://github.com/davidchambers/Base64.js
-  var digits =
-    'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
-  window.atob = function (input) {
-    input = input.replace(/=+$/, '');
-    if (input.length % 4 === 1) {
-      throw new Error('bad atob input');
-    }
-    for (
-      // initialize result and counters
-      var bc = 0, bs, buffer, idx = 0, output = '';
-      // get next character
-      (buffer = input.charAt(idx++));
-      // character found in table?
-      // initialize bit storage and add its ascii value
-      ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer,
-        // and if not first of each 4 characters,
-        // convert the first 8 bits to one ascii character
-        bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0
-    ) {
-      // try to find character in table (0-63, not found => -1)
-      buffer = digits.indexOf(buffer);
-    }
-    return output;
-  };
-})();
-
-// Function.prototype.bind?
-// Support: Android<4.0, iOS<6.0
-(function checkFunctionPrototypeBindCompatibility() {
-  if (typeof Function.prototype.bind !== 'undefined') {
-    return;
-  }
-
-  Function.prototype.bind = function functionPrototypeBind(obj) {
-    var fn = this, headArgs = Array.prototype.slice.call(arguments, 1);
-    var bound = function functionPrototypeBindBound() {
-      var args = headArgs.concat(Array.prototype.slice.call(arguments));
-      return fn.apply(obj, args);
-    };
-    return bound;
-  };
-})();
-
-// HTMLElement dataset property
-// Support: IE<11, Safari<5.1, Android<4.0
-(function checkDatasetProperty() {
-  var div = document.createElement('div');
-  if ('dataset' in div) {
-    return; // dataset property exists
-  }
-
-  Object.defineProperty(HTMLElement.prototype, 'dataset', {
-    get: function() {
-      if (this._dataset) {
-        return this._dataset;
-      }
-
-      var dataset = {};
-      for (var j = 0, jj = this.attributes.length; j < jj; j++) {
-        var attribute = this.attributes[j];
-        if (attribute.name.substring(0, 5) !== 'data-') {
-          continue;
-        }
-        var key = attribute.name.substring(5).replace(/\-([a-z])/g,
-          function(all, ch) {
-            return ch.toUpperCase();
-          });
-        dataset[key] = attribute.value;
-      }
-
-      Object.defineProperty(this, '_dataset', {
-        value: dataset,
-        writable: false,
-        enumerable: false
-      });
-      return dataset;
-    },
-    enumerable: true
-  });
-})();
-
-// HTMLElement classList property
-// Support: IE<10, Android<4.0, iOS<5.0
-(function checkClassListProperty() {
-  var div = document.createElement('div');
-  if ('classList' in div) {
-    return; // classList property exists
-  }
-
-  function changeList(element, itemName, add, remove) {
-    var s = element.className || '';
-    var list = s.split(/\s+/g);
-    if (list[0] === '') {
-      list.shift();
-    }
-    var index = list.indexOf(itemName);
-    if (index < 0 && add) {
-      list.push(itemName);
-    }
-    if (index >= 0 && remove) {
-      list.splice(index, 1);
-    }
-    element.className = list.join(' ');
-    return (index >= 0);
-  }
-
-  var classListPrototype = {
-    add: function(name) {
-      changeList(this.element, name, true, false);
-    },
-    contains: function(name) {
-      return changeList(this.element, name, false, false);
-    },
-    remove: function(name) {
-      changeList(this.element, name, false, true);
-    },
-    toggle: function(name) {
-      changeList(this.element, name, true, true);
-    }
-  };
-
-  Object.defineProperty(HTMLElement.prototype, 'classList', {
-    get: function() {
-      if (this._classList) {
-        return this._classList;
-      }
-
-      var classList = Object.create(classListPrototype, {
-        element: {
-          value: this,
-          writable: false,
-          enumerable: true
-        }
-      });
-      Object.defineProperty(this, '_classList', {
-        value: classList,
-        writable: false,
-        enumerable: false
-      });
-      return classList;
-    },
-    enumerable: true
-  });
-})();
-
-// Check console compatibility
-// In older IE versions the console object is not available
-// unless console is open.
-// Support: IE<10
-(function checkConsoleCompatibility() {
-  if (!('console' in window)) {
-    window.console = {
-      log: function() {},
-      error: function() {},
-      warn: function() {}
-    };
-  } else if (!('bind' in console.log)) {
-    // native functions in IE9 might not have bind
-    console.log = (function(fn) {
-      return function(msg) {
-        return fn(msg);
-      };
-    })(console.log);
-    console.error = (function(fn) {
-      return function(msg) {
-        return fn(msg);
-      };
-    })(console.error);
-    console.warn = (function(fn) {
-      return function(msg) {
-        return fn(msg);
-      };
-    })(console.warn);
-  }
-})();
-
-// Check onclick compatibility in Opera
-// Support: Opera<15
-(function checkOnClickCompatibility() {
-  // workaround for reported Opera bug DSK-354448:
-  // onclick fires on disabled buttons with opaque content
-  function ignoreIfTargetDisabled(event) {
-    if (isDisabled(event.target)) {
-      event.stopPropagation();
-    }
-  }
-  function isDisabled(node) {
-    return node.disabled || (node.parentNode && isDisabled(node.parentNode));
-  }
-  if (isOpera) {
-    // use browser detection since we cannot feature-check this bug
-    document.addEventListener('click', ignoreIfTargetDisabled, true);
-  }
-})();
-
-// Checks if possible to use URL.createObjectURL()
-// Support: IE, Chrome on iOS
-(function checkOnBlobSupport() {
-  // sometimes IE and Chrome on iOS loosing the data created with
-  // createObjectURL(), see #3977 and #8081
-  if (isIE || isIOSChrome) {
-    PDFJS.disableCreateObjectURL = true;
-  }
-})();
-
-// Checks if navigator.language is supported
-(function checkNavigatorLanguage() {
-  if ('language' in navigator) {
-    return;
-  }
-  PDFJS.locale = navigator.userLanguage || 'en-US';
-})();
-
-// Support: Safari 6.0+, Android<3.0, Chrome 39/40, iOS
-(function checkRangeRequests() {
-  // Safari has issues with cached range requests see:
-  // https://github.com/mozilla/pdf.js/issues/3260
-  // Last tested with version 6.0.4.
-
-  // Older versions of Android (pre 3.0) has issues with range requests, see:
-  // https://github.com/mozilla/pdf.js/issues/3381.
-  // Make sure that we only match webkit-based Android browsers,
-  // since Firefox/Fennec works as expected.
-
-  // Range requests are broken in Chrome 39 and 40, https://crbug.com/442318
-  if (isSafari || isAndroidPre3 || isChromeWithRangeBug || isIOS) {
-    PDFJS.disableRange = true;
-    PDFJS.disableStream = true;
-  }
-})();
-
-// Check if the browser supports manipulation of the history.
-// Support: IE<10, Android<4.2
-(function checkHistoryManipulation() {
-  // Android 2.x has so buggy pushState support that it was removed in
-  // Android 3.0 and restored as late as in Android 4.2.
-  // Support: Android 2.x
-  if (!history.pushState || isAndroidPre3) {
-    PDFJS.disableHistory = true;
-  }
-})();
-
-// Support: IE<11, Chrome<21, Android<4.4, Safari<6
-(function checkSetPresenceInImageData() {
-  // IE < 11 will use window.CanvasPixelArray which lacks set function.
-  if (window.CanvasPixelArray) {
-    if (typeof window.CanvasPixelArray.prototype.set !== 'function') {
-      window.CanvasPixelArray.prototype.set = function(arr) {
-        for (var i = 0, ii = this.length; i < ii; i++) {
-          this[i] = arr[i];
-        }
-      };
-    }
-  } else {
-    // Old Chrome and Android use an inaccessible CanvasPixelArray prototype.
-    // Because we cannot feature detect it, we rely on user agent parsing.
-    var polyfill = false, versionMatch;
-    if (isChrome) {
-      versionMatch = userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
-      // Chrome < 21 lacks the set function.
-      polyfill = versionMatch && parseInt(versionMatch[2]) < 21;
-    } else if (isAndroid) {
-      // Android < 4.4 lacks the set function.
-      // Android >= 4.4 will contain Chrome in the user agent,
-      // thus pass the Chrome check above and not reach this block.
-      polyfill = isAndroidPre5;
-    } else if (isSafari) {
-      versionMatch = userAgent.
-        match(/Version\/([0-9]+)\.([0-9]+)\.([0-9]+) Safari\//);
-      // Safari < 6 lacks the set function.
-      polyfill = versionMatch && parseInt(versionMatch[1]) < 6;
-    }
-
-    if (polyfill) {
-      var contextPrototype = window.CanvasRenderingContext2D.prototype;
-      var createImageData = contextPrototype.createImageData;
-      contextPrototype.createImageData = function(w, h) {
-        var imageData = createImageData.call(this, w, h);
-        imageData.data.set = function(arr) {
-          for (var i = 0, ii = this.length; i < ii; i++) {
-            this[i] = arr[i];
-          }
-        };
-        return imageData;
-      };
-      // this closure will be kept referenced, so clear its vars
-      contextPrototype = null;
-    }
-  }
-})();
-
-// Support: IE<10, Android<4.0, iOS
-(function checkRequestAnimationFrame() {
-  function fakeRequestAnimationFrame(callback) {
-    window.setTimeout(callback, 20);
-  }
-
-  if (isIOS) {
-    // requestAnimationFrame on iOS is broken, replacing with fake one.
-    window.requestAnimationFrame = fakeRequestAnimationFrame;
-    return;
-  }
-  if ('requestAnimationFrame' in window) {
-    return;
-  }
-  window.requestAnimationFrame =
-    window.mozRequestAnimationFrame ||
-    window.webkitRequestAnimationFrame ||
-    fakeRequestAnimationFrame;
-})();
-
-// Support: Android, iOS
-(function checkCanvasSizeLimitation() {
-  if (isIOS || isAndroid) {
-    // 5MP
-    PDFJS.maxCanvasPixels = 5242880;
-  }
-})();
-
-// Disable fullscreen support for certain problematic configurations.
-// Support: IE11+ (when embedded).
-(function checkFullscreenSupport() {
-  if (isIE && window.parent !== window) {
-    PDFJS.disableFullscreen = true;
-  }
-})();
-
-// Provides document.currentScript support
-// Support: IE, Chrome<29.
-(function checkCurrentScript() {
-  if ('currentScript' in document) {
-    return;
-  }
-  Object.defineProperty(document, 'currentScript', {
-    get: function () {
-      var scripts = document.getElementsByTagName('script');
-      return scripts[scripts.length - 1];
-    },
-    enumerable: true,
-    configurable: true
-  });
-})();
-
-// Provides `input.type = 'type'` runtime failure protection.
-// Support: IE9,10.
-(function checkInputTypeNumberAssign() {
-  var el = document.createElement('input');
-  try {
-    el.type = 'number';
-  } catch (ex) {
-    var inputProto = el.constructor.prototype;
-    var typeProperty = Object.getOwnPropertyDescriptor(inputProto, 'type');
-    Object.defineProperty(inputProto, 'type', {
-      get: function () {
-        return typeProperty.get.call(this);
-      },
-      set: function (value) {
-        typeProperty.set.call(this, value === 'number' ? 'text' : value);
-      },
-      enumerable: true,
-      configurable: true
-    });
-  }
-})();
-
-// Provides correct document.readyState value for legacy browsers.
-// Support: IE9,10.
-(function checkDocumentReadyState() {
-  if (!document.attachEvent) {
-    return;
-  }
-  var documentProto = document.constructor.prototype;
-  var readyStateProto = Object.getOwnPropertyDescriptor(documentProto,
-                                                        'readyState');
-  Object.defineProperty(documentProto, 'readyState', {
-    get: function () {
-      var value = readyStateProto.get.call(this);
-      return value === 'interactive' ? 'loading' : value;
-    },
-    set: function (value) {
-      readyStateProto.set.call(this, value);
-    },
-    enumerable: true,
-    configurable: true
-  });
-})();
-
-// Provides support for ChildNode.remove in legacy browsers.
-// Support: IE.
-(function checkChildNodeRemove() {
-  if (typeof Element.prototype.remove !== 'undefined') {
-    return;
-  }
-  Element.prototype.remove = function () {
-    if (this.parentNode) {
-      this.parentNode.removeChild(this);
-    }
-  };
-})();
-
-}).call((typeof window === 'undefined') ? this : window);
+require('../src/shared/compatibility.js');
diff --git a/web/viewer.html b/web/viewer.html
index 22ff8af8e..876ebb0d5 100644
--- a/web/viewer.html
+++ b/web/viewer.html
@@ -44,8 +44,8 @@ See https://github.com/adobe-type-tools/cmap-resources
     <link rel="resource" type="application/l10n" href="locale/locale.properties">
 <!--#endif-->
 
-<!--#if !(FIREFOX || MOZCENTRAL || CHROME || MINIFIED)-->
-    <script src="compatibility.js"></script>
+<!--#if !PRODUCTION-->
+    <script src="../src/shared/compatibility.js"></script>
 <!--#endif-->
 
 <!--#if !PRODUCTION-->