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-->