You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1854 lines
52 KiB
1854 lines
52 KiB
/* 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 Uint32ArrayView(buffer, length) { |
|
this.buffer = buffer; |
|
this.byteLength = buffer.length; |
|
this.length = length; |
|
ensureUint32ArrayViewProps(this.length); |
|
} |
|
Uint32ArrayView.prototype = Object.create(null); |
|
|
|
var uint32ArrayViewSetters = 0; |
|
function createUint32ArrayProp(index) { |
|
return { |
|
get() { |
|
var buffer = this.buffer, offset = index << 2; |
|
return (buffer[offset] | (buffer[offset + 1] << 8) | |
|
(buffer[offset + 2] << 16) | (buffer[offset + 3] << 24)) >>> 0; |
|
}, |
|
set(value) { |
|
var buffer = this.buffer, offset = index << 2; |
|
buffer[offset] = value & 255; |
|
buffer[offset + 1] = (value >> 8) & 255; |
|
buffer[offset + 2] = (value >> 16) & 255; |
|
buffer[offset + 3] = (value >>> 24) & 255; |
|
} |
|
}; |
|
} |
|
|
|
function ensureUint32ArrayViewProps(length) { |
|
while (uint32ArrayViewSetters < length) { |
|
Object.defineProperty(Uint32ArrayView.prototype, |
|
uint32ArrayViewSetters, |
|
createUint32ArrayProp(uint32ArrayViewSetters)); |
|
uint32ArrayViewSetters++; |
|
} |
|
} |
|
|
|
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.Int32Array = TypedArray; |
|
globalScope.Uint16Array = TypedArray; |
|
globalScope.Float32Array = TypedArray; |
|
globalScope.Float64Array = TypedArray; |
|
|
|
globalScope.Uint32Array = function () { |
|
if (arguments.length === 3) { |
|
// Building view for buffer, offset, and length |
|
if (arguments[1] !== 0) { |
|
throw new Error('offset !== 0 is not supported'); |
|
} |
|
return new Uint32ArrayView(arguments[0], arguments[2]); |
|
} |
|
return TypedArray.apply(this, arguments); |
|
}; |
|
})(); |
|
|
|
// window.CanvasPixelArray.buffer/.byteLength |
|
// Support: IE9 |
|
(function canvasPixelArrayBuffer() { |
|
if (!hasDOM || !window.CanvasPixelArray) { |
|
return; |
|
} |
|
var cpaProto = window.CanvasPixelArray.prototype; |
|
if ('buffer' in cpaProto) { |
|
return; |
|
} |
|
// Trying to fake CanvasPixelArray as Uint8ClampedArray. |
|
Object.defineProperty(cpaProto, 'buffer', { |
|
get() { |
|
return this; |
|
}, |
|
enumerable: false, |
|
configurable: true |
|
}); |
|
Object.defineProperty(cpaProto, 'byteLength', { |
|
get() { |
|
return this.length; |
|
}, |
|
enumerable: false, |
|
configurable: true |
|
}); |
|
})(); |
|
|
|
// 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() { |
|
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(name) { |
|
changeList(this.element, name, true, false); |
|
}, |
|
contains(name) { |
|
return changeList(this.element, name, false, false); |
|
}, |
|
remove(name) { |
|
changeList(this.element, name, false, true); |
|
}, |
|
toggle(name) { |
|
changeList(this.element, name, true, true); |
|
} |
|
}; |
|
|
|
Object.defineProperty(HTMLElement.prototype, 'classList', { |
|
get() { |
|
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() {}, |
|
error() {}, |
|
warn() {} |
|
}; |
|
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 installFakeAnimationFrameFunctions() { |
|
window.requestAnimationFrame = function (callback) { |
|
return window.setTimeout(callback, 20); |
|
}; |
|
window.cancelAnimationFrame = function (timeoutID) { |
|
window.clearTimeout(timeoutID); |
|
}; |
|
} |
|
|
|
if (!hasDOM) { |
|
return; |
|
} |
|
if (isIOS) { |
|
// requestAnimationFrame on iOS is broken, replacing with fake one. |
|
installFakeAnimationFrameFunctions(); |
|
return; |
|
} |
|
if ('requestAnimationFrame' in window) { |
|
return; |
|
} |
|
window.requestAnimationFrame = window.mozRequestAnimationFrame || |
|
window.webkitRequestAnimationFrame; |
|
if (window.requestAnimationFrame) { |
|
return; |
|
} |
|
installFakeAnimationFrameFunctions(); |
|
})(); |
|
|
|
// 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() { |
|
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() { |
|
return typeProperty.get.call(this); |
|
}, |
|
set(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() { |
|
var value = readyStateProto.get.call(this); |
|
return value === 'interactive' ? 'loading' : value; |
|
}, |
|
set(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, |
|
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, |
|
onReject, |
|
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(obj) { |
|
return !!Object.getOwnPropertyDescriptor(obj, this.id); |
|
}, |
|
get(obj, defaultValue) { |
|
return this.has(obj) ? obj[this.id] : defaultValue; |
|
}, |
|
set(obj, value) { |
|
Object.defineProperty(obj, this.id, { |
|
value, |
|
enumerable: false, |
|
configurable: true |
|
}); |
|
}, |
|
delete(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 (c === EOF) { |
|
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 (c !== EOF && c !== '\t' && c !== '\n' && c !== '\r') { |
|
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 (this._scheme !== 'file') { |
|
this._scheme = base._scheme; |
|
} |
|
if (c === EOF) { |
|
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 (this._scheme !== 'file' || !ALPHA.test(c) || |
|
(nextC !== ':' && nextC !== '|') || |
|
(nextNextC !== EOF && 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 (this._scheme !== 'file') { |
|
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 (this._password !== null) { |
|
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 (c !== '\t' && c !== '\n' && c !== '\r') { |
|
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 (c !== '\t' && c !== '\n' && c !== '\r') { |
|
buffer += percentEscape(c); |
|
} |
|
break; |
|
|
|
case 'query': |
|
if (!stateOverride && c === '#') { |
|
this._fragment = '#'; |
|
state = 'fragment'; |
|
} else if (c !== EOF && c !== '\t' && c !== '\n' && c !== '\r') { |
|
this._query += percentEscapeQuery(c); |
|
} |
|
break; |
|
|
|
case 'fragment': |
|
if (c !== EOF && c !== '\t' && c !== '\n' && c !== '\r') { |
|
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() { |
|
return this.href; |
|
}, |
|
get href() { |
|
if (this._isInvalid) { |
|
return this._url; |
|
} |
|
var authority = ''; |
|
if (this._username !== '' || this._password !== null) { |
|
authority = this._username + |
|
(this._password !== null ? ':' + 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; |
|
})(); |
|
|
|
} |
|
|
|
}));
|
|
|