Spaces:
Sleeping
Sleeping
/*! | |
* modernizr v3.13.0 | |
* Build https://modernizr.com/download?-audio-bloburls-boxshadow-boxsizing-canvas-cookies-cssanimations-csscalc-csstransforms-csstransforms3d-csstransitions-datauri-fetch-filereader-filesystem-flexbox-fullscreen-geolocation-hashchange-history-indexeddb-json-localstorage-notification-performance-placeholder-pointerevents-promises-queryselector-scriptasync-scriptdefer-serviceworker-sessionstorage-stylescoped-svg-templatestrings-touchevents-typedarrays-video-webgl-webp-webpalpha-websockets-websqldatabase-xhr2-dontmin | |
* | |
* Copyright (c) | |
* Faruk Ates | |
* Paul Irish | |
* Alex Sexton | |
* Ryan Seddon | |
* Patrick Kettner | |
* Stu Cox | |
* Richard Herrera | |
* Veeck | |
* MIT License | |
*/ | |
/* | |
* Modernizr tests which native CSS3 and HTML5 features are available in the | |
* current UA and makes the results available to you in two ways: as properties on | |
* a global `Modernizr` object, and as classes on the `<html>` element. This | |
* information allows you to progressively enhance your pages with a granular level | |
* of control over the experience. | |
*/ | |
;(function(scriptGlobalObject, window, document, undefined){ | |
var tests = []; | |
/** | |
* ModernizrProto is the constructor for Modernizr | |
* | |
* @class | |
* @access public | |
*/ | |
var ModernizrProto = { | |
_version: '3.13.0', | |
// Any settings that don't work as separate modules | |
// can go in here as configuration. | |
_config: { | |
'classPrefix': '', | |
'enableClasses': false, | |
'enableJSClass': true, | |
'usePrefixes': true | |
}, | |
// Queue of tests | |
_q: [], | |
// Stub these for people who are listening | |
on: function(test, cb) { | |
// I don't really think people should do this, but we can | |
// safe guard it a bit. | |
// -- NOTE:: this gets WAY overridden in src/addTest for actual async tests. | |
// This is in case people listen to synchronous tests. I would leave it out, | |
// but the code to *disallow* sync tests in the real version of this | |
// function is actually larger than this. | |
var self = this; | |
setTimeout(function() { | |
cb(self[test]); | |
}, 0); | |
}, | |
addTest: function(name, fn, options) { | |
tests.push({name: name, fn: fn, options: options}); | |
}, | |
addAsyncTest: function(fn) { | |
tests.push({name: null, fn: fn}); | |
} | |
}; | |
// Fake some of Object.create so we can force non test results to be non "own" properties. | |
var Modernizr = function() {}; | |
Modernizr.prototype = ModernizrProto; | |
// Leak modernizr globally when you `require` it rather than force it here. | |
// Overwrite name so constructor name is nicer :D | |
Modernizr = new Modernizr(); | |
var classes = []; | |
/** | |
* is returns a boolean if the typeof an obj is exactly type. | |
* | |
* @access private | |
* @function is | |
* @param {*} obj - A thing we want to check the type of | |
* @param {string} type - A string to compare the typeof against | |
* @returns {boolean} true if the typeof the first parameter is exactly the specified type, false otherwise | |
*/ | |
function is(obj, type) { | |
return typeof obj === type; | |
} | |
; | |
/** | |
* Run through all tests and detect their support in the current UA. | |
* | |
* @access private | |
* @returns {void} | |
*/ | |
function testRunner() { | |
var featureNames; | |
var feature; | |
var aliasIdx; | |
var result; | |
var nameIdx; | |
var featureName; | |
var featureNameSplit; | |
for (var featureIdx in tests) { | |
if (tests.hasOwnProperty(featureIdx)) { | |
featureNames = []; | |
feature = tests[featureIdx]; | |
// run the test, throw the return value into the Modernizr, | |
// then based on that boolean, define an appropriate className | |
// and push it into an array of classes we'll join later. | |
// | |
// If there is no name, it's an 'async' test that is run, | |
// but not directly added to the object. That should | |
// be done with a post-run addTest call. | |
if (feature.name) { | |
featureNames.push(feature.name.toLowerCase()); | |
if (feature.options && feature.options.aliases && feature.options.aliases.length) { | |
// Add all the aliases into the names list | |
for (aliasIdx = 0; aliasIdx < feature.options.aliases.length; aliasIdx++) { | |
featureNames.push(feature.options.aliases[aliasIdx].toLowerCase()); | |
} | |
} | |
} | |
// Run the test, or use the raw value if it's not a function | |
result = is(feature.fn, 'function') ? feature.fn() : feature.fn; | |
// Set each of the names on the Modernizr object | |
for (nameIdx = 0; nameIdx < featureNames.length; nameIdx++) { | |
featureName = featureNames[nameIdx]; | |
// Support dot properties as sub tests. We don't do checking to make sure | |
// that the implied parent tests have been added. You must call them in | |
// order (either in the test, or make the parent test a dependency). | |
// | |
// Cap it to TWO to make the logic simple and because who needs that kind of subtesting | |
// hashtag famous last words | |
featureNameSplit = featureName.split('.'); | |
if (featureNameSplit.length === 1) { | |
Modernizr[featureNameSplit[0]] = result; | |
} else { | |
// cast to a Boolean, if not one already or if it doesnt exist yet (like inputtypes) | |
if (!Modernizr[featureNameSplit[0]] || Modernizr[featureNameSplit[0]] && !(Modernizr[featureNameSplit[0]] instanceof Boolean)) { | |
Modernizr[featureNameSplit[0]] = new Boolean(Modernizr[featureNameSplit[0]]); | |
} | |
Modernizr[featureNameSplit[0]][featureNameSplit[1]] = result; | |
} | |
classes.push((result ? '' : 'no-') + featureNameSplit.join('-')); | |
} | |
} | |
} | |
} | |
; | |
/** | |
* docElement is a convenience wrapper to grab the root element of the document | |
* | |
* @access private | |
* @returns {HTMLElement|SVGElement} The root element of the document | |
*/ | |
var docElement = document.documentElement; | |
/** | |
* A convenience helper to check if the document we are running in is an SVG document | |
* | |
* @access private | |
* @returns {boolean} | |
*/ | |
var isSVG = docElement.nodeName.toLowerCase() === 'svg'; | |
/** | |
* createElement is a convenience wrapper around document.createElement. Since we | |
* use createElement all over the place, this allows for (slightly) smaller code | |
* as well as abstracting away issues with creating elements in contexts other than | |
* HTML documents (e.g. SVG documents). | |
* | |
* @access private | |
* @function createElement | |
* @returns {HTMLElement|SVGElement} An HTML or SVG element | |
*/ | |
function createElement() { | |
if (typeof document.createElement !== 'function') { | |
// This is the case in IE7, where the type of createElement is "object". | |
// For this reason, we cannot call apply() as Object is not a Function. | |
return document.createElement(arguments[0]); | |
} else if (isSVG) { | |
return document.createElementNS.call(document, 'http://www.w3.org/2000/svg', arguments[0]); | |
} else { | |
return document.createElement.apply(document, arguments); | |
} | |
} | |
; | |
/*! | |
{ | |
"name": "HTML5 Audio Element", | |
"property": "audio", | |
"caniuse": "audio", | |
"tags": ["html5", "audio", "media"], | |
"notes": [{ | |
"name": "MDN Docs", | |
"href": "https://developer.mozilla.org/En/Media_formats_supported_by_the_audio_and_video_elements" | |
}] | |
} | |
!*/ | |
/* DOC | |
Detects support of the audio element, as well as testing what types of content it supports. | |
Subproperties are provided to describe support for `ogg`, `mp3`,`opus`, `wav` and `m4a` formats, e.g.: | |
```javascript | |
Modernizr.audio // true | |
Modernizr.audio.ogg // 'probably' | |
``` | |
*/ | |
// Codec values from : github.com/NielsLeenheer/html5test/blob/9106a8/index.html#L845 | |
// thx to NielsLeenheer and zcorpan | |
// Note: in some older browsers, "no" was a return value instead of empty string. | |
// It was live in FF3.5.0 and 3.5.1, but fixed in 3.5.2 | |
// It was also live in Safari 4.0.0 - 4.0.4, but fixed in 4.0.5 | |
(function() { | |
var elem = createElement('audio'); | |
Modernizr.addTest('audio', function() { | |
var bool = false; | |
try { | |
bool = !!elem.canPlayType; | |
if (bool) { | |
bool = new Boolean(bool); | |
} | |
} catch (e) {} | |
return bool; | |
}); | |
// IE9 Running on Windows Server SKU can cause an exception to be thrown, bug #224 | |
try { | |
if (!!elem.canPlayType) { | |
Modernizr.addTest('audio.ogg', elem.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/, '')); | |
Modernizr.addTest('audio.mp3', elem.canPlayType('audio/mpeg; codecs="mp3"').replace(/^no$/, '')); | |
Modernizr.addTest('audio.opus', elem.canPlayType('audio/ogg; codecs="opus"') || | |
elem.canPlayType('audio/webm; codecs="opus"').replace(/^no$/, '')); | |
Modernizr.addTest('audio.wav', elem.canPlayType('audio/wav; codecs="1"').replace(/^no$/, '')); | |
Modernizr.addTest('audio.m4a', (elem.canPlayType('audio/x-m4a;') || | |
elem.canPlayType('audio/aac;')).replace(/^no$/, '')); | |
} | |
} catch (e) {} | |
})(); | |
/*! | |
{ | |
"name": "Canvas", | |
"property": "canvas", | |
"caniuse": "canvas", | |
"tags": ["canvas", "graphics"], | |
"polyfills": ["flashcanvas", "excanvas", "slcanvas", "fxcanvas"] | |
} | |
!*/ | |
/* DOC | |
Detects support for the `<canvas>` element for 2D drawing. | |
*/ | |
// On the S60 and BB Storm, getContext exists, but always returns undefined | |
// so we actually have to call getContext() to verify | |
// github.com/Modernizr/Modernizr/issues/issue/97/ | |
Modernizr.addTest('canvas', function() { | |
var elem = createElement('canvas'); | |
return !!(elem.getContext && elem.getContext('2d')); | |
}); | |
/*! | |
{ | |
"name": "Cookies", | |
"property": "cookies", | |
"tags": ["storage"], | |
"authors": ["tauren"] | |
} | |
!*/ | |
/* DOC | |
Detects whether cookie support is enabled. | |
*/ | |
// https://github.com/Modernizr/Modernizr/issues/191 | |
Modernizr.addTest('cookies', function() { | |
// navigator.cookieEnabled cannot detect custom or nuanced cookie blocking | |
// configurations. For example, when blocking cookies via the Advanced | |
// Privacy Settings in IE9, it always returns true. And there have been | |
// issues in the past with site-specific exceptions. | |
// Don't rely on it. | |
// try..catch because some in situations `document.cookie` is exposed but throws a | |
// SecurityError if you try to access it; e.g. documents created from data URIs | |
// or in sandboxed iframes (depending on flags/context) | |
try { | |
// Create cookie | |
document.cookie = 'cookietest=1'; | |
var ret = document.cookie.indexOf('cookietest=') !== -1; | |
// Delete cookie | |
document.cookie = 'cookietest=1; expires=Thu, 01-Jan-1970 00:00:01 GMT'; | |
return ret; | |
} | |
catch (e) { | |
return false; | |
} | |
}); | |
/** | |
* If the browsers follow the spec, then they would expose vendor-specific styles as: | |
* elem.style.WebkitBorderRadius | |
* instead of something like the following (which is technically incorrect): | |
* elem.style.webkitBorderRadius | |
* | |
* WebKit ghosts their properties in lowercase but Opera & Moz do not. | |
* Microsoft uses a lowercase `ms` instead of the correct `Ms` in IE8+ | |
* erik.eae.net/archives/2008/03/10/21.48.10/ | |
* | |
* More here: github.com/Modernizr/Modernizr/issues/issue/21 | |
* | |
* @access private | |
* @returns {string} The string representing the vendor-specific style properties | |
*/ | |
var omPrefixes = 'Moz O ms Webkit'; | |
var cssomPrefixes = (ModernizrProto._config.usePrefixes ? omPrefixes.split(' ') : []); | |
ModernizrProto._cssomPrefixes = cssomPrefixes; | |
/** | |
* contains checks to see if a string contains another string | |
* | |
* @access private | |
* @function contains | |
* @param {string} str - The string we want to check for substrings | |
* @param {string} substr - The substring we want to search the first string for | |
* @returns {boolean} true if and only if the first string 'str' contains the second string 'substr' | |
*/ | |
function contains(str, substr) { | |
return !!~('' + str).indexOf(substr); | |
} | |
; | |
/** | |
* Create our "modernizr" element that we do most feature tests on. | |
* | |
* @access private | |
*/ | |
var modElem = { | |
elem: createElement('modernizr') | |
}; | |
// Clean up this element | |
Modernizr._q.push(function() { | |
delete modElem.elem; | |
}); | |
var mStyle = { | |
style: modElem.elem.style | |
}; | |
// kill ref for gc, must happen before mod.elem is removed, so we unshift on to | |
// the front of the queue. | |
Modernizr._q.unshift(function() { | |
delete mStyle.style; | |
}); | |
/** | |
* getBody returns the body of a document, or an element that can stand in for | |
* the body if a real body does not exist | |
* | |
* @access private | |
* @function getBody | |
* @returns {HTMLElement|SVGElement} Returns the real body of a document, or an | |
* artificially created element that stands in for the body | |
*/ | |
function getBody() { | |
// After page load injecting a fake body doesn't work so check if body exists | |
var body = document.body; | |
if (!body) { | |
// Can't use the real body create a fake one. | |
body = createElement(isSVG ? 'svg' : 'body'); | |
body.fake = true; | |
} | |
return body; | |
} | |
; | |
/** | |
* injectElementWithStyles injects an element with style element and some CSS rules | |
* | |
* @access private | |
* @function injectElementWithStyles | |
* @param {string} rule - String representing a css rule | |
* @param {Function} callback - A function that is used to test the injected element | |
* @param {number} [nodes] - An integer representing the number of additional nodes you want injected | |
* @param {string[]} [testnames] - An array of strings that are used as ids for the additional nodes | |
* @returns {boolean} the result of the specified callback test | |
*/ | |
function injectElementWithStyles(rule, callback, nodes, testnames) { | |
var mod = 'modernizr'; | |
var style; | |
var ret; | |
var node; | |
var docOverflow; | |
var div = createElement('div'); | |
var body = getBody(); | |
if (parseInt(nodes, 10)) { | |
// In order not to give false positives we create a node for each test | |
// This also allows the method to scale for unspecified uses | |
while (nodes--) { | |
node = createElement('div'); | |
node.id = testnames ? testnames[nodes] : mod + (nodes + 1); | |
div.appendChild(node); | |
} | |
} | |
style = createElement('style'); | |
style.type = 'text/css'; | |
style.id = 's' + mod; | |
// IE6 will false positive on some tests due to the style element inside the test div somehow interfering offsetHeight, so insert it into body or fakebody. | |
// Opera will act all quirky when injecting elements in documentElement when page is served as xml, needs fakebody too. #270 | |
(!body.fake ? div : body).appendChild(style); | |
body.appendChild(div); | |
if (style.styleSheet) { | |
style.styleSheet.cssText = rule; | |
} else { | |
style.appendChild(document.createTextNode(rule)); | |
} | |
div.id = mod; | |
if (body.fake) { | |
//avoid crashing IE8, if background image is used | |
body.style.background = ''; | |
//Safari 5.13/5.1.4 OSX stops loading if ::-webkit-scrollbar is used and scrollbars are visible | |
body.style.overflow = 'hidden'; | |
docOverflow = docElement.style.overflow; | |
docElement.style.overflow = 'hidden'; | |
docElement.appendChild(body); | |
} | |
ret = callback(div, rule); | |
// If this is done after page load we don't want to remove the body so check if body exists | |
if (body.fake && body.parentNode) { | |
body.parentNode.removeChild(body); | |
docElement.style.overflow = docOverflow; | |
// Trigger layout so kinetic scrolling isn't disabled in iOS6+ | |
// eslint-disable-next-line | |
docElement.offsetHeight; | |
} else { | |
div.parentNode.removeChild(div); | |
} | |
return !!ret; | |
} | |
; | |
/** | |
* domToCSS takes a camelCase string and converts it to hyphen-case | |
* e.g. boxSizing -> box-sizing | |
* | |
* @access private | |
* @function domToCSS | |
* @param {string} name - String name of camelCase prop we want to convert | |
* @returns {string} The hyphen-case version of the supplied name | |
*/ | |
function domToCSS(name) { | |
return name.replace(/([A-Z])/g, function(str, m1) { | |
return '-' + m1.toLowerCase(); | |
}).replace(/^ms-/, '-ms-'); | |
} | |
; | |
/** | |
* wrapper around getComputedStyle, to fix issues with Firefox returning null when | |
* called inside of a hidden iframe | |
* | |
* @access private | |
* @function computedStyle | |
* @param {HTMLElement|SVGElement} elem - The element we want to find the computed styles of | |
* @param {string|null} [pseudo] - An optional pseudo element selector (e.g. :before), of null if none | |
* @param {string} prop - A CSS property | |
* @returns {CSSStyleDeclaration} the value of the specified CSS property | |
*/ | |
function computedStyle(elem, pseudo, prop) { | |
var result; | |
if ('getComputedStyle' in window) { | |
result = getComputedStyle.call(window, elem, pseudo); | |
var console = window.console; | |
if (result !== null) { | |
if (prop) { | |
result = result.getPropertyValue(prop); | |
} | |
} else { | |
if (console) { | |
var method = console.error ? 'error' : 'log'; | |
console[method].call(console, 'getComputedStyle returning null, its possible modernizr test results are inaccurate'); | |
} | |
} | |
} else { | |
result = !pseudo && elem.currentStyle && elem.currentStyle[prop]; | |
} | |
return result; | |
} | |
; | |
/** | |
* nativeTestProps allows for us to use native feature detection functionality if available. | |
* some prefixed form, or false, in the case of an unsupported rule | |
* | |
* @access private | |
* @function nativeTestProps | |
* @param {Array} props - An array of property names | |
* @param {string} value - A string representing the value we want to check via @supports | |
* @returns {boolean|undefined} A boolean when @supports exists, undefined otherwise | |
*/ | |
// Accepts a list of property names and a single value | |
// Returns `undefined` if native detection not available | |
function nativeTestProps(props, value) { | |
var i = props.length; | |
// Start with the JS API: https://www.w3.org/TR/css3-conditional/#the-css-interface | |
if ('CSS' in window && 'supports' in window.CSS) { | |
// Try every prefixed variant of the property | |
while (i--) { | |
if (window.CSS.supports(domToCSS(props[i]), value)) { | |
return true; | |
} | |
} | |
return false; | |
} | |
// Otherwise fall back to at-rule (for Opera 12.x) | |
else if ('CSSSupportsRule' in window) { | |
// Build a condition string for every prefixed variant | |
var conditionText = []; | |
while (i--) { | |
conditionText.push('(' + domToCSS(props[i]) + ':' + value + ')'); | |
} | |
conditionText = conditionText.join(' or '); | |
return injectElementWithStyles('@supports (' + conditionText + ') { #modernizr { position: absolute; } }', function(node) { | |
return computedStyle(node, null, 'position') === 'absolute'; | |
}); | |
} | |
return undefined; | |
} | |
; | |
/** | |
* cssToDOM takes a hyphen-case string and converts it to camelCase | |
* e.g. box-sizing -> boxSizing | |
* | |
* @access private | |
* @function cssToDOM | |
* @param {string} name - String name of hyphen-case prop we want to convert | |
* @returns {string} The camelCase version of the supplied name | |
*/ | |
function cssToDOM(name) { | |
return name.replace(/([a-z])-([a-z])/g, function(str, m1, m2) { | |
return m1 + m2.toUpperCase(); | |
}).replace(/^-/, ''); | |
} | |
; | |
// testProps is a generic CSS / DOM property test. | |
// In testing support for a given CSS property, it's legit to test: | |
// `elem.style[styleName] !== undefined` | |
// If the property is supported it will return an empty string, | |
// if unsupported it will return undefined. | |
// We'll take advantage of this quick test and skip setting a style | |
// on our modernizr element, but instead just testing undefined vs | |
// empty string. | |
// Property names can be provided in either camelCase or hyphen-case. | |
function testProps(props, prefixed, value, skipValueTest) { | |
skipValueTest = is(skipValueTest, 'undefined') ? false : skipValueTest; | |
// Try native detect first | |
if (!is(value, 'undefined')) { | |
var result = nativeTestProps(props, value); | |
if (!is(result, 'undefined')) { | |
return result; | |
} | |
} | |
// Otherwise do it properly | |
var afterInit, i, propsLength, prop, before; | |
// If we don't have a style element, that means we're running async or after | |
// the core tests, so we'll need to create our own elements to use. | |
// Inside of an SVG element, in certain browsers, the `style` element is only | |
// defined for valid tags. Therefore, if `modernizr` does not have one, we | |
// fall back to a less used element and hope for the best. | |
// For strict XHTML browsers the hardly used samp element is used. | |
var elems = ['modernizr', 'tspan', 'samp']; | |
while (!mStyle.style && elems.length) { | |
afterInit = true; | |
mStyle.modElem = createElement(elems.shift()); | |
mStyle.style = mStyle.modElem.style; | |
} | |
// Delete the objects if we created them. | |
function cleanElems() { | |
if (afterInit) { | |
delete mStyle.style; | |
delete mStyle.modElem; | |
} | |
} | |
propsLength = props.length; | |
for (i = 0; i < propsLength; i++) { | |
prop = props[i]; | |
before = mStyle.style[prop]; | |
if (contains(prop, '-')) { | |
prop = cssToDOM(prop); | |
} | |
if (mStyle.style[prop] !== undefined) { | |
// If value to test has been passed in, do a set-and-check test. | |
// 0 (integer) is a valid property value, so check that `value` isn't | |
// undefined, rather than just checking it's truthy. | |
if (!skipValueTest && !is(value, 'undefined')) { | |
// Needs a try catch block because of old IE. This is slow, but will | |
// be avoided in most cases because `skipValueTest` will be used. | |
try { | |
mStyle.style[prop] = value; | |
} catch (e) {} | |
// If the property value has changed, we assume the value used is | |
// supported. If `value` is empty string, it'll fail here (because | |
// it hasn't changed), which matches how browsers have implemented | |
// CSS.supports() | |
if (mStyle.style[prop] !== before) { | |
cleanElems(); | |
return prefixed === 'pfx' ? prop : true; | |
} | |
} | |
// Otherwise just return true, or the property name if this is a | |
// `prefixed()` call | |
else { | |
cleanElems(); | |
return prefixed === 'pfx' ? prop : true; | |
} | |
} | |
} | |
cleanElems(); | |
return false; | |
} | |
; | |
/** | |
* List of JavaScript DOM values used for tests | |
* | |
* @memberOf Modernizr | |
* @name Modernizr._domPrefixes | |
* @optionName Modernizr._domPrefixes | |
* @optionProp domPrefixes | |
* @access public | |
* @example | |
* | |
* Modernizr._domPrefixes is exactly the same as [_prefixes](#modernizr-_prefixes), but rather | |
* than hyphen-case properties, all properties are their Capitalized variant | |
* | |
* ```js | |
* Modernizr._domPrefixes === [ "Moz", "O", "ms", "Webkit" ]; | |
* ``` | |
*/ | |
var domPrefixes = (ModernizrProto._config.usePrefixes ? omPrefixes.toLowerCase().split(' ') : []); | |
ModernizrProto._domPrefixes = domPrefixes; | |
/** | |
* fnBind is a super small [bind](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) polyfill. | |
* | |
* @access private | |
* @function fnBind | |
* @param {Function} fn - a function you want to change `this` reference to | |
* @param {object} that - the `this` you want to call the function with | |
* @returns {Function} The wrapped version of the supplied function | |
*/ | |
function fnBind(fn, that) { | |
return function() { | |
return fn.apply(that, arguments); | |
}; | |
} | |
; | |
/** | |
* testDOMProps is a generic DOM property test; if a browser supports | |
* a certain property, it won't return undefined for it. | |
* | |
* @access private | |
* @function testDOMProps | |
* @param {Array<string>} props - An array of properties to test for | |
* @param {object} obj - An object or Element you want to use to test the parameters again | |
* @param {boolean|object} elem - An Element to bind the property lookup again. Use `false` to prevent the check | |
* @returns {boolean|*} returns `false` if the prop is unsupported, otherwise the value that is supported | |
*/ | |
function testDOMProps(props, obj, elem) { | |
var item; | |
for (var i in props) { | |
if (props[i] in obj) { | |
// return the property name as a string | |
if (elem === false) { | |
return props[i]; | |
} | |
item = obj[props[i]]; | |
// let's bind a function | |
if (is(item, 'function')) { | |
// bind to obj unless overridden | |
return fnBind(item, elem || obj); | |
} | |
// return the unbound function or obj or value | |
return item; | |
} | |
} | |
return false; | |
} | |
; | |
/** | |
* testPropsAll tests a list of DOM properties we want to check against. | |
* We specify literally ALL possible (known and/or likely) properties on | |
* the element including the non-vendor prefixed one, for forward- | |
* compatibility. | |
* | |
* @access private | |
* @function testPropsAll | |
* @param {string} prop - A string of the property to test for | |
* @param {string|object} [prefixed] - An object to check the prefixed properties on. Use a string to skip | |
* @param {HTMLElement|SVGElement} [elem] - An element used to test the property and value against | |
* @param {string} [value] - A string of a css value | |
* @param {boolean} [skipValueTest] - An boolean representing if you want to test if value sticks when set | |
* @returns {string|boolean} returns the string version of the property, or `false` if it is unsupported | |
*/ | |
function testPropsAll(prop, prefixed, elem, value, skipValueTest) { | |
var ucProp = prop.charAt(0).toUpperCase() + prop.slice(1), | |
props = (prop + ' ' + cssomPrefixes.join(ucProp + ' ') + ucProp).split(' '); | |
// did they call .prefixed('boxSizing') or are we just testing a prop? | |
if (is(prefixed, 'string') || is(prefixed, 'undefined')) { | |
return testProps(props, prefixed, value, skipValueTest); | |
// otherwise, they called .prefixed('requestAnimationFrame', window[, elem]) | |
} else { | |
props = (prop + ' ' + (domPrefixes).join(ucProp + ' ') + ucProp).split(' '); | |
return testDOMProps(props, prefixed, elem); | |
} | |
} | |
// Modernizr.testAllProps() investigates whether a given style property, | |
// or any of its vendor-prefixed variants, is recognized | |
// | |
// Note that the property names must be provided in the camelCase variant. | |
// Modernizr.testAllProps('boxSizing') | |
ModernizrProto.testAllProps = testPropsAll; | |
/** | |
* testAllProps determines whether a given CSS property is supported in the browser | |
* | |
* @memberOf Modernizr | |
* @name Modernizr.testAllProps | |
* @optionName Modernizr.testAllProps() | |
* @optionProp testAllProps | |
* @access public | |
* @function testAllProps | |
* @param {string} prop - String naming the property to test (either camelCase or hyphen-case) | |
* @param {string} [value] - String of the value to test | |
* @param {boolean} [skipValueTest=false] - Whether to skip testing that the value is supported when using non-native detection | |
* @returns {string|boolean} returns the string version of the property, or `false` if it is unsupported | |
* @example | |
* | |
* testAllProps determines whether a given CSS property, in some prefixed form, | |
* is supported by the browser. | |
* | |
* ```js | |
* testAllProps('boxSizing') // true | |
* ``` | |
* | |
* It can optionally be given a CSS value in string form to test if a property | |
* value is valid | |
* | |
* ```js | |
* testAllProps('display', 'block') // true | |
* testAllProps('display', 'penguin') // false | |
* ``` | |
* | |
* A boolean can be passed as a third parameter to skip the value check when | |
* native detection (@supports) isn't available. | |
* | |
* ```js | |
* testAllProps('shapeOutside', 'content-box', true); | |
* ``` | |
*/ | |
function testAllProps(prop, value, skipValueTest) { | |
return testPropsAll(prop, undefined, undefined, value, skipValueTest); | |
} | |
ModernizrProto.testAllProps = testAllProps; | |
/*! | |
{ | |
"name": "CSS Animations", | |
"property": "cssanimations", | |
"caniuse": "css-animation", | |
"polyfills": ["transformie", "csssandpaper"], | |
"tags": ["css"], | |
"warnings": ["Android < 4 will pass this test, but can only animate a single property at a time"], | |
"notes": [{ | |
"name": "Article: 'Dispelling the Android CSS animation myths'", | |
"href": "https://web.archive.org/web/20180602074607/https://daneden.me/2011/12/14/putting-up-with-androids-bullshit/" | |
}] | |
} | |
!*/ | |
/* DOC | |
Detects whether or not elements can be animated using CSS | |
*/ | |
Modernizr.addTest('cssanimations', testAllProps('animationName', 'a', true)); | |
/*! | |
{ | |
"name": "Box Shadow", | |
"property": "boxshadow", | |
"caniuse": "css-boxshadow", | |
"tags": ["css"], | |
"knownBugs": [ | |
"WebOS false positives on this test.", | |
"The Kindle Silk browser false positives" | |
] | |
} | |
!*/ | |
Modernizr.addTest('boxshadow', testAllProps('boxShadow', '1px 1px', true)); | |
/*! | |
{ | |
"name": "Box Sizing", | |
"property": "boxsizing", | |
"caniuse": "css3-boxsizing", | |
"polyfills": ["borderboxmodel", "boxsizingpolyfill", "borderbox"], | |
"tags": ["css"], | |
"builderAliases": ["css_boxsizing"], | |
"notes": [{ | |
"name": "MDN Docs", | |
"href": "https://developer.mozilla.org/en-US/docs/Web/CSS/box-sizing" | |
}, { | |
"name": "Related Github Issue", | |
"href": "https://github.com/Modernizr/Modernizr/issues/248" | |
}] | |
} | |
!*/ | |
Modernizr.addTest('boxsizing', testAllProps('boxSizing', 'border-box', true) && (document.documentMode === undefined || document.documentMode > 7)); | |
/** | |
* List of property values to set for css tests. See ticket #21 | |
* https://github.com/modernizr/modernizr/issues/21 | |
* | |
* @memberOf Modernizr | |
* @name Modernizr._prefixes | |
* @optionName Modernizr._prefixes | |
* @optionProp prefixes | |
* @access public | |
* @example | |
* | |
* Modernizr._prefixes is the internal list of prefixes that we test against | |
* inside of things like [prefixed](#modernizr-prefixed) and [prefixedCSS](#-code-modernizr-prefixedcss). It is simply | |
* an array of hyphen-case vendor prefixes you can use within your code. | |
* | |
* Some common use cases include | |
* | |
* Generating all possible prefixed version of a CSS property | |
* ```js | |
* var rule = Modernizr._prefixes.join('transform: rotate(20deg); '); | |
* | |
* rule === 'transform: rotate(20deg); webkit-transform: rotate(20deg); moz-transform: rotate(20deg); o-transform: rotate(20deg); ms-transform: rotate(20deg);' | |
* ``` | |
* | |
* Generating all possible prefixed version of a CSS value | |
* ```js | |
* rule = 'display:' + Modernizr._prefixes.join('flex; display:') + 'flex'; | |
* | |
* rule === 'display:flex; display:-webkit-flex; display:-moz-flex; display:-o-flex; display:-ms-flex; display:flex' | |
* ``` | |
*/ | |
// we use ['',''] rather than an empty array in order to allow a pattern of .`join()`ing prefixes to test | |
// values in feature detects to continue to work | |
var prefixes = (ModernizrProto._config.usePrefixes ? ' -webkit- -moz- -o- -ms- '.split(' ') : ['','']); | |
// expose these for the plugin API. Look in the source for how to join() them against your input | |
ModernizrProto._prefixes = prefixes; | |
/*! | |
{ | |
"name": "CSS Calc", | |
"property": "csscalc", | |
"caniuse": "calc", | |
"tags": ["css"], | |
"builderAliases": ["css_calc"], | |
"authors": ["@calvein"] | |
} | |
!*/ | |
/* DOC | |
Method of allowing calculated values for length units. For example: | |
```css | |
//lem { | |
width: calc(100% - 3em); | |
} | |
``` | |
*/ | |
Modernizr.addTest('csscalc', function() { | |
var prop = 'width:'; | |
var value = 'calc(10px);'; | |
var el = createElement('a'); | |
el.style.cssText = prop + prefixes.join(value + prop); | |
return !!el.style.length; | |
}); | |
/*! | |
{ | |
"name": "Flexbox", | |
"property": "flexbox", | |
"caniuse": "flexbox", | |
"tags": ["css"], | |
"notes": [{ | |
"name": "The _new_ flexbox", | |
"href": "https://www.w3.org/TR/css-flexbox-1/" | |
}], | |
"warnings": [ | |
"A `true` result for this detect does not imply that the `flex-wrap` property is supported; see the `flexwrap` detect." | |
] | |
} | |
!*/ | |
/* DOC | |
Detects support for the Flexible Box Layout model, a.k.a. Flexbox, which allows easy manipulation of layout order and sizing within a container. | |
*/ | |
Modernizr.addTest('flexbox', testAllProps('flexBasis', '1px', true)); | |
/*! | |
{ | |
"name": "CSS Transforms", | |
"property": "csstransforms", | |
"caniuse": "transforms2d", | |
"tags": ["css"] | |
} | |
!*/ | |
Modernizr.addTest('csstransforms', function() { | |
// Android < 3.0 is buggy, so we sniff and reject it | |
// https://github.com/Modernizr/Modernizr/issues/903 | |
return navigator.userAgent.indexOf('Android 2.') === -1 && | |
testAllProps('transform', 'scale(1)', true); | |
}); | |
/*! | |
{ | |
"name": "CSS Supports", | |
"property": "supports", | |
"caniuse": "css-featurequeries", | |
"tags": ["css"], | |
"builderAliases": ["css_supports"], | |
"notes": [{ | |
"name": "W3C Spec (The @supports rule)", | |
"href": "https://dev.w3.org/csswg/css3-conditional/#at-supports" | |
}, { | |
"name": "Related Github Issue", | |
"href": "https://github.com/Modernizr/Modernizr/issues/648" | |
}, { | |
"name": "W3C Spec (The CSSSupportsRule interface)", | |
"href": "https://dev.w3.org/csswg/css3-conditional/#the-csssupportsrule-interface" | |
}] | |
} | |
!*/ | |
var newSyntax = 'CSS' in window && 'supports' in window.CSS; | |
var oldSyntax = 'supportsCSS' in window; | |
Modernizr.addTest('supports', newSyntax || oldSyntax); | |
/*! | |
{ | |
"name": "CSS Transforms 3D", | |
"property": "csstransforms3d", | |
"caniuse": "transforms3d", | |
"tags": ["css"], | |
"warnings": [ | |
"Chrome may occasionally fail this test on some systems; more info: https://bugs.chromium.org/p/chromium/issues/detail?id=129004" | |
] | |
} | |
!*/ | |
Modernizr.addTest('csstransforms3d', function() { | |
return !!testAllProps('perspective', '1px', true); | |
}); | |
/*! | |
{ | |
"name": "CSS Transitions", | |
"property": "csstransitions", | |
"caniuse": "css-transitions", | |
"tags": ["css"] | |
} | |
!*/ | |
Modernizr.addTest('csstransitions', testAllProps('transition', 'all', true)); | |
/*! | |
{ | |
"name": "ES6 Promises", | |
"property": "promises", | |
"caniuse": "promises", | |
"polyfills": ["es6promises"], | |
"authors": ["Krister Kari", "Jake Archibald"], | |
"tags": ["es6"], | |
"notes": [{ | |
"name": "The ES6 promises spec", | |
"href": "https://github.com/domenic/promises-unwrapping" | |
}, { | |
"name": "Chromium dashboard - ES6 Promises", | |
"href": "https://www.chromestatus.com/features/5681726336532480" | |
}, { | |
"name": "JavaScript Promises: an Introduction", | |
"href": "https://developers.google.com/web/fundamentals/primers/promises/" | |
}] | |
} | |
!*/ | |
/* DOC | |
Check if browser implements ECMAScript 6 Promises per specification. | |
*/ | |
Modernizr.addTest('promises', function() { | |
return 'Promise' in window && | |
// Some of these methods are missing from | |
// Firefox/Chrome experimental implementations | |
'resolve' in window.Promise && | |
'reject' in window.Promise && | |
'all' in window.Promise && | |
'race' in window.Promise && | |
// Older version of the spec had a resolver object | |
// as the arg rather than a function | |
(function() { | |
var resolve; | |
new window.Promise(function(r) { resolve = r; }); | |
return typeof resolve === 'function'; | |
}()); | |
}); | |
/*! | |
{ | |
"name": "File API", | |
"property": "filereader", | |
"caniuse": "fileapi", | |
"notes": [{ | |
"name": "W3C Working Draft Spec", | |
"href": "https://www.w3.org/TR/FileAPI/" | |
}], | |
"tags": ["file"], | |
"builderAliases": ["file_api"], | |
"knownBugs": ["Will fail in Safari 5 due to its lack of support for the standards defined FileReader object"] | |
} | |
!*/ | |
/* DOC | |
`filereader` tests for the File API specification | |
Tests for objects specific to the File API W3C specification without | |
being redundant (don't bother testing for Blob since it is assumed | |
to be the File object's prototype.) | |
*/ | |
Modernizr.addTest('filereader', !!(window.File && window.FileList && window.FileReader)); | |
/** | |
* atRule returns a given CSS property at-rule (eg @keyframes), possibly in | |
* some prefixed form, or false, in the case of an unsupported rule | |
* | |
* @memberOf Modernizr | |
* @name Modernizr.atRule | |
* @optionName Modernizr.atRule() | |
* @optionProp atRule | |
* @access public | |
* @function atRule | |
* @param {string} prop - String name of the @-rule to test for | |
* @returns {string|boolean} The string representing the (possibly prefixed) | |
* valid version of the @-rule, or `false` when it is unsupported. | |
* @example | |
* ```js | |
* var keyframes = Modernizr.atRule('@keyframes'); | |
* | |
* if (keyframes) { | |
* // keyframes are supported | |
* // could be `@-webkit-keyframes` or `@keyframes` | |
* } else { | |
* // keyframes === `false` | |
* } | |
* ``` | |
*/ | |
var atRule = function(prop) { | |
var length = prefixes.length; | |
var cssrule = window.CSSRule; | |
var rule; | |
if (typeof cssrule === 'undefined') { | |
return undefined; | |
} | |
if (!prop) { | |
return false; | |
} | |
// remove literal @ from beginning of provided property | |
prop = prop.replace(/^@/, ''); | |
// CSSRules use underscores instead of dashes | |
rule = prop.replace(/-/g, '_').toUpperCase() + '_RULE'; | |
if (rule in cssrule) { | |
return '@' + prop; | |
} | |
for (var i = 0; i < length; i++) { | |
// prefixes gives us something like -o-, and we want O_ | |
var prefix = prefixes[i]; | |
var thisRule = prefix.toUpperCase() + '_' + rule; | |
if (thisRule in cssrule) { | |
return '@-' + prefix.toLowerCase() + '-' + prop; | |
} | |
} | |
return false; | |
}; | |
ModernizrProto.atRule = atRule; | |
/** | |
* prefixed returns the prefixed or nonprefixed property name variant of your input | |
* | |
* @memberOf Modernizr | |
* @name Modernizr.prefixed | |
* @optionName Modernizr.prefixed() | |
* @optionProp prefixed | |
* @access public | |
* @function prefixed | |
* @param {string} prop - String name of the property to test for | |
* @param {object} [obj] - An object to test for the prefixed properties on | |
* @param {HTMLElement} [elem] - An element used to test specific properties against | |
* @returns {string|boolean} The string representing the (possibly prefixed) valid | |
* version of the property, or `false` when it is unsupported. | |
* @example | |
* | |
* Modernizr.prefixed takes a string css value in the DOM style camelCase (as | |
* opposed to the css style hyphen-case) form and returns the (possibly prefixed) | |
* version of that property that the browser actually supports. | |
* | |
* For example, in older Firefox... | |
* ```js | |
* prefixed('boxSizing') | |
* ``` | |
* returns 'MozBoxSizing' | |
* | |
* In newer Firefox, as well as any other browser that support the unprefixed | |
* version would simply return `boxSizing`. Any browser that does not support | |
* the property at all, it will return `false`. | |
* | |
* By default, prefixed is checked against a DOM element. If you want to check | |
* for a property on another object, just pass it as a second argument | |
* | |
* ```js | |
* var rAF = prefixed('requestAnimationFrame', window); | |
* | |
* raf(function() { | |
* renderFunction(); | |
* }) | |
* ``` | |
* | |
* Note that this will return _the actual function_ - not the name of the function. | |
* If you need the actual name of the property, pass in `false` as a third argument | |
* | |
* ```js | |
* var rAFProp = prefixed('requestAnimationFrame', window, false); | |
* | |
* rafProp === 'WebkitRequestAnimationFrame' // in older webkit | |
* ``` | |
* | |
* One common use case for prefixed is if you're trying to determine which transition | |
* end event to bind to, you might do something like... | |
* ```js | |
* var transEndEventNames = { | |
* 'WebkitTransition' : 'webkitTransitionEnd', * Saf 6, Android Browser | |
* 'MozTransition' : 'transitionend', * only for FF < 15 | |
* 'transition' : 'transitionend' * IE10, Opera, Chrome, FF 15+, Saf 7+ | |
* }; | |
* | |
* var transEndEventName = transEndEventNames[ Modernizr.prefixed('transition') ]; | |
* ``` | |
* | |
* If you want a similar lookup, but in hyphen-case, you can use [prefixedCSS](#modernizr-prefixedcss). | |
*/ | |
var prefixed = ModernizrProto.prefixed = function(prop, obj, elem) { | |
if (prop.indexOf('@') === 0) { | |
return atRule(prop); | |
} | |
if (prop.indexOf('-') !== -1) { | |
// Convert hyphen-case to camelCase | |
prop = cssToDOM(prop); | |
} | |
if (!obj) { | |
return testPropsAll(prop, 'pfx'); | |
} else { | |
// Testing DOM property e.g. Modernizr.prefixed('requestAnimationFrame', window) // 'mozRequestAnimationFrame' | |
return testPropsAll(prop, obj, elem); | |
} | |
}; | |
/*! | |
{ | |
"name": "Filesystem API", | |
"property": "filesystem", | |
"caniuse": "filesystem", | |
"notes": [{ | |
"name": "W3C Spec", | |
"href": "https://www.w3.org/TR/file-system-api/" | |
}], | |
"authors": ["Eric Bidelman (@ebidel)"], | |
"tags": ["file"], | |
"builderAliases": ["file_filesystem"], | |
"knownBugs": ["The API will be present in Chrome incognito, but will throw an exception. See crbug.com/93417"] | |
} | |
!*/ | |
Modernizr.addTest('filesystem', !!prefixed('requestFileSystem', window)); | |
/*! | |
{ | |
"name": "placeholder attribute", | |
"property": "placeholder", | |
"tags": ["forms", "attribute"], | |
"builderAliases": ["forms_placeholder"] | |
} | |
!*/ | |
/* DOC | |
Tests for placeholder attribute in inputs and textareas | |
*/ | |
Modernizr.addTest('placeholder', ('placeholder' in createElement('input') && 'placeholder' in createElement('textarea'))); | |
/*! | |
{ | |
"name": "Fullscreen API", | |
"property": "fullscreen", | |
"caniuse": "fullscreen", | |
"notes": [{ | |
"name": "MDN Docs", | |
"href": "https://developer.mozilla.org/en/API/Fullscreen" | |
}], | |
"polyfills": ["screenfulljs"], | |
"builderAliases": ["fullscreen_api"] | |
} | |
!*/ | |
/* DOC | |
Detects support for the ability to make the current website take over the user's entire screen | |
*/ | |
// github.com/Modernizr/Modernizr/issues/739 | |
Modernizr.addTest('fullscreen', !!(prefixed('exitFullscreen', document, false) || prefixed('cancelFullScreen', document, false))); | |
/*! | |
{ | |
"name": "Geolocation API", | |
"property": "geolocation", | |
"caniuse": "geolocation", | |
"tags": ["media"], | |
"notes": [{ | |
"name": "MDN Docs", | |
"href": "https://developer.mozilla.org/en-US/docs/WebAPI/Using_geolocation" | |
}], | |
"polyfills": [ | |
"joshuabell-polyfill", | |
"webshims", | |
"geo-location-javascript", | |
"geolocation-api-polyfill" | |
] | |
} | |
!*/ | |
/* DOC | |
Detects support for the Geolocation API for users to provide their location to web applications. | |
*/ | |
// geolocation is often considered a trivial feature detect... | |
// Turns out, it's quite tricky to get right: | |
// | |
// Using !!navigator.geolocation does two things we don't want. It: | |
// 1. Leaks memory in IE9: github.com/Modernizr/Modernizr/issues/513 | |
// 2. Disables page caching in WebKit: webk.it/43956 | |
// | |
// Meanwhile, in Firefox < 8, an about:config setting could expose | |
// a false positive that would throw an exception: bugzil.la/688158 | |
Modernizr.addTest('geolocation', 'geolocation' in navigator); | |
/** | |
* Modernizr.hasEvent() detects support for a given event | |
* | |
* @memberOf Modernizr | |
* @name Modernizr.hasEvent | |
* @optionName Modernizr.hasEvent() | |
* @optionProp hasEvent | |
* @access public | |
* @function hasEvent | |
* @param {string|*} eventName - the name of an event to test for (e.g. "resize") | |
* @param {Element|string} [element=HTMLDivElement] - is the element|document|window|tagName to test on | |
* @returns {boolean} | |
* @example | |
* `Modernizr.hasEvent` lets you determine if the browser supports a supplied event. | |
* By default, it does this detection on a div element | |
* | |
* ```js | |
* hasEvent('blur') // true; | |
* ``` | |
* | |
* However, you are able to give an object as a second argument to hasEvent to | |
* detect an event on something other than a div. | |
* | |
* ```js | |
* hasEvent('devicelight', window) // true; | |
* ``` | |
*/ | |
var hasEvent = (function() { | |
// Detect whether event support can be detected via `in`. Test on a DOM element | |
// using the "blur" event b/c it should always exist. bit.ly/event-detection | |
var needsFallback = !('onblur' in docElement); | |
function inner(eventName, element) { | |
var isSupported; | |
if (!eventName) { return false; } | |
if (!element || typeof element === 'string') { | |
element = createElement(element || 'div'); | |
} | |
// Testing via the `in` operator is sufficient for modern browsers and IE. | |
// When using `setAttribute`, IE skips "unload", WebKit skips "unload" and | |
// "resize", whereas `in` "catches" those. | |
eventName = 'on' + eventName; | |
isSupported = eventName in element; | |
// Fallback technique for old Firefox - bit.ly/event-detection | |
if (!isSupported && needsFallback) { | |
if (!element.setAttribute) { | |
// Switch to generic element if it lacks `setAttribute`. | |
// It could be the `document`, `window`, or something else. | |
element = createElement('div'); | |
} | |
element.setAttribute(eventName, ''); | |
isSupported = typeof element[eventName] === 'function'; | |
if (element[eventName] !== undefined) { | |
// If property was created, "remove it" by setting value to `undefined`. | |
element[eventName] = undefined; | |
} | |
element.removeAttribute(eventName); | |
} | |
return isSupported; | |
} | |
return inner; | |
})(); | |
ModernizrProto.hasEvent = hasEvent; | |
/*! | |
{ | |
"name": "Hashchange event", | |
"property": "hashchange", | |
"caniuse": "hashchange", | |
"tags": ["history"], | |
"notes": [{ | |
"name": "MDN Docs", | |
"href": "https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onhashchange" | |
}], | |
"polyfills": [ | |
"jquery-hashchange", | |
"moo-historymanager", | |
"jquery-ajaxy", | |
"hasher", | |
"shistory" | |
] | |
} | |
!*/ | |
/* DOC | |
Detects support for the `hashchange` event, fired when the current location fragment changes. | |
*/ | |
Modernizr.addTest('hashchange', function() { | |
if (hasEvent('hashchange', window) === false) { | |
return false; | |
} | |
// documentMode logic from YUI to filter out IE8 Compat Mode | |
// which false positives. | |
return (document.documentMode === undefined || document.documentMode > 7); | |
}); | |
/*! | |
{ | |
"name": "History API", | |
"property": "history", | |
"caniuse": "history", | |
"tags": ["history"], | |
"authors": ["Hay Kranen", "Alexander Farkas"], | |
"notes": [{ | |
"name": "W3C Spec", | |
"href": "https://www.w3.org/TR/html51/browsers.html#the-history-interface" | |
}, { | |
"name": "MDN Docs", | |
"href": "https://developer.mozilla.org/en-US/docs/Web/API/window.history" | |
}], | |
"polyfills": ["historyjs", "html5historyapi"] | |
} | |
!*/ | |
/* DOC | |
Detects support for the History API for manipulating the browser session history. | |
*/ | |
Modernizr.addTest('history', function() { | |
// Issue #733 | |
// The stock browser on Android 2.2 & 2.3, and 4.0.x returns positive on history support | |
// Unfortunately support is really buggy and there is no clean way to detect | |
// these bugs, so we fall back to a user agent sniff :( | |
var ua = navigator.userAgent; | |
// Some browsers allow to have empty userAgent. | |
// Therefore, we need to check ua before using "indexOf" on it. | |
if(!ua) { | |
return false; | |
} | |
// We only want Android 2 and 4.0, stock browser, and not Chrome which identifies | |
// itself as 'Mobile Safari' as well, nor Windows Phone (issue #1471). | |
if ((ua.indexOf('Android 2.') !== -1 || | |
(ua.indexOf('Android 4.0') !== -1)) && | |
ua.indexOf('Mobile Safari') !== -1 && | |
ua.indexOf('Chrome') === -1 && | |
ua.indexOf('Windows Phone') === -1 && | |
// Since all documents on file:// share an origin, the History apis are | |
// blocked there as well | |
location.protocol !== 'file:' | |
) { | |
return false; | |
} | |
// Return the regular check | |
return (window.history && 'pushState' in window.history); | |
}); | |
/** | |
* hasOwnProp is a shim for hasOwnProperty that is needed for Safari 2.0 support | |
* | |
* @author kangax | |
* @access private | |
* @function hasOwnProp | |
* @param {object} object - The object to check for a property | |
* @param {string} property - The property to check for | |
* @returns {boolean} | |
*/ | |
// hasOwnProperty shim by kangax needed for Safari 2.0 support | |
var hasOwnProp; | |
(function() { | |
var _hasOwnProperty = ({}).hasOwnProperty; | |
/* istanbul ignore else */ | |
/* we have no way of testing IE 5.5 or safari 2, | |
* so just assume the else gets hit */ | |
if (!is(_hasOwnProperty, 'undefined') && !is(_hasOwnProperty.call, 'undefined')) { | |
hasOwnProp = function(object, property) { | |
return _hasOwnProperty.call(object, property); | |
}; | |
} | |
else { | |
hasOwnProp = function(object, property) { /* yes, this can give false positives/negatives, but most of the time we don't care about those */ | |
return ((property in object) && is(object.constructor.prototype[property], 'undefined')); | |
}; | |
} | |
})(); | |
/** | |
* setClasses takes an array of class names and adds them to the root element | |
* | |
* @access private | |
* @function setClasses | |
* @param {string[]} classes - Array of class names | |
*/ | |
// Pass in an and array of class names, e.g.: | |
// ['no-webp', 'borderradius', ...] | |
function setClasses(classes) { | |
var className = docElement.className; | |
var classPrefix = Modernizr._config.classPrefix || ''; | |
if (isSVG) { | |
className = className.baseVal; | |
} | |
// Change `no-js` to `js` (independently of the `enableClasses` option) | |
// Handle classPrefix on this too | |
if (Modernizr._config.enableJSClass) { | |
var reJS = new RegExp('(^|\\s)' + classPrefix + 'no-js(\\s|$)'); | |
className = className.replace(reJS, '$1' + classPrefix + 'js$2'); | |
} | |
if (Modernizr._config.enableClasses) { | |
// Add the new classes | |
if (classes.length > 0) { | |
className += ' ' + classPrefix + classes.join(' ' + classPrefix); | |
} | |
if (isSVG) { | |
docElement.className.baseVal = className; | |
} else { | |
docElement.className = className; | |
} | |
} | |
} | |
; | |
// _l tracks listeners for async tests, as well as tests that execute after the initial run | |
ModernizrProto._l = {}; | |
/** | |
* Modernizr.on is a way to listen for the completion of async tests. Being | |
* asynchronous, they may not finish before your scripts run. As a result you | |
* will get a possibly false negative `undefined` value. | |
* | |
* @memberOf Modernizr | |
* @name Modernizr.on | |
* @access public | |
* @function on | |
* @param {string} feature - String name of the feature detect | |
* @param {Function} cb - Callback function returning a Boolean - true if feature is supported, false if not | |
* @returns {void} | |
* @example | |
* | |
* ```js | |
* Modernizr.on('flash', function( result ) { | |
* if (result) { | |
* // the browser has flash | |
* } else { | |
* // the browser does not have flash | |
* } | |
* }); | |
* ``` | |
*/ | |
ModernizrProto.on = function(feature, cb) { | |
// Create the list of listeners if it doesn't exist | |
if (!this._l[feature]) { | |
this._l[feature] = []; | |
} | |
// Push this test on to the listener list | |
this._l[feature].push(cb); | |
// If it's already been resolved, trigger it on next tick | |
if (Modernizr.hasOwnProperty(feature)) { | |
// Next Tick | |
setTimeout(function() { | |
Modernizr._trigger(feature, Modernizr[feature]); | |
}, 0); | |
} | |
}; | |
/** | |
* _trigger is the private function used to signal test completion and run any | |
* callbacks registered through [Modernizr.on](#modernizr-on) | |
* | |
* @memberOf Modernizr | |
* @name Modernizr._trigger | |
* @access private | |
* @function _trigger | |
* @param {string} feature - string name of the feature detect | |
* @param {Function|boolean} [res] - A feature detection function, or the boolean = | |
* result of a feature detection function | |
* @returns {void} | |
*/ | |
ModernizrProto._trigger = function(feature, res) { | |
if (!this._l[feature]) { | |
return; | |
} | |
var cbs = this._l[feature]; | |
// Force async | |
setTimeout(function() { | |
var i, cb; | |
for (i = 0; i < cbs.length; i++) { | |
cb = cbs[i]; | |
cb(res); | |
} | |
}, 0); | |
// Don't trigger these again | |
delete this._l[feature]; | |
}; | |
/** | |
* addTest allows you to define your own feature detects that are not currently | |
* included in Modernizr (under the covers it's the exact same code Modernizr | |
* uses for its own [feature detections](https://github.com/Modernizr/Modernizr/tree/master/feature-detects)). | |
* Just like the official detects, the result | |
* will be added onto the Modernizr object, as well as an appropriate className set on | |
* the html element when configured to do so | |
* | |
* @memberOf Modernizr | |
* @name Modernizr.addTest | |
* @optionName Modernizr.addTest() | |
* @optionProp addTest | |
* @access public | |
* @function addTest | |
* @param {string|object} feature - The string name of the feature detect, or an | |
* object of feature detect names and test | |
* @param {Function|boolean} test - Function returning true if feature is supported, | |
* false if not. Otherwise a boolean representing the results of a feature detection | |
* @returns {object} the Modernizr object to allow chaining | |
* @example | |
* | |
* The most common way of creating your own feature detects is by calling | |
* `Modernizr.addTest` with a string (preferably just lowercase, without any | |
* punctuation), and a function you want executed that will return a boolean result | |
* | |
* ```js | |
* Modernizr.addTest('itsTuesday', function() { | |
* var d = new Date(); | |
* return d.getDay() === 2; | |
* }); | |
* ``` | |
* | |
* When the above is run, it will set Modernizr.itstuesday to `true` when it is tuesday, | |
* and to `false` every other day of the week. One thing to notice is that the names of | |
* feature detect functions are always lowercased when added to the Modernizr object. That | |
* means that `Modernizr.itsTuesday` will not exist, but `Modernizr.itstuesday` will. | |
* | |
* | |
* Since we only look at the returned value from any feature detection function, | |
* you do not need to actually use a function. For simple detections, just passing | |
* in a statement that will return a boolean value works just fine. | |
* | |
* ```js | |
* Modernizr.addTest('hasjquery', 'jQuery' in window); | |
* ``` | |
* | |
* Just like before, when the above runs `Modernizr.hasjquery` will be true if | |
* jQuery has been included on the page. Not using a function saves a small amount | |
* of overhead for the browser, as well as making your code much more readable. | |
* | |
* Finally, you also have the ability to pass in an object of feature names and | |
* their tests. This is handy if you want to add multiple detections in one go. | |
* The keys should always be a string, and the value can be either a boolean or | |
* function that returns a boolean. | |
* | |
* ```js | |
* var detects = { | |
* 'hasjquery': 'jQuery' in window, | |
* 'itstuesday': function() { | |
* var d = new Date(); | |
* return d.getDay() === 2; | |
* } | |
* } | |
* | |
* Modernizr.addTest(detects); | |
* ``` | |
* | |
* There is really no difference between the first methods and this one, it is | |
* just a convenience to let you write more readable code. | |
*/ | |
function addTest(feature, test) { | |
if (typeof feature === 'object') { | |
for (var key in feature) { | |
if (hasOwnProp(feature, key)) { | |
addTest(key, feature[ key ]); | |
} | |
} | |
} else { | |
feature = feature.toLowerCase(); | |
var featureNameSplit = feature.split('.'); | |
var last = Modernizr[featureNameSplit[0]]; | |
// Again, we don't check for parent test existence. Get that right, though. | |
if (featureNameSplit.length === 2) { | |
last = last[featureNameSplit[1]]; | |
} | |
if (typeof last !== 'undefined') { | |
// we're going to quit if you're trying to overwrite an existing test | |
// if we were to allow it, we'd do this: | |
// var re = new RegExp("\\b(no-)?" + feature + "\\b"); | |
// docElement.className = docElement.className.replace( re, '' ); | |
// but, no rly, stuff 'em. | |
return Modernizr; | |
} | |
test = typeof test === 'function' ? test() : test; | |
// Set the value (this is the magic, right here). | |
if (featureNameSplit.length === 1) { | |
Modernizr[featureNameSplit[0]] = test; | |
} else { | |
// cast to a Boolean, if not one already | |
if (Modernizr[featureNameSplit[0]] && !(Modernizr[featureNameSplit[0]] instanceof Boolean)) { | |
Modernizr[featureNameSplit[0]] = new Boolean(Modernizr[featureNameSplit[0]]); | |
} | |
Modernizr[featureNameSplit[0]][featureNameSplit[1]] = test; | |
} | |
// Set a single class (either `feature` or `no-feature`) | |
setClasses([(!!test && test !== false ? '' : 'no-') + featureNameSplit.join('-')]); | |
// Trigger the event | |
Modernizr._trigger(feature, test); | |
} | |
return Modernizr; // allow chaining. | |
} | |
// After all the tests are run, add self to the Modernizr prototype | |
Modernizr._q.push(function() { | |
ModernizrProto.addTest = addTest; | |
}); | |
/*! | |
{ | |
"name": "Webp", | |
"async": true, | |
"property": "webp", | |
"caniuse": "webp", | |
"tags": ["image"], | |
"builderAliases": ["img_webp"], | |
"authors": ["Krister Kari", "@amandeep", "Rich Bradshaw", "Ryan Seddon", "Paul Irish"], | |
"notes": [{ | |
"name": "Webp Info", | |
"href": "https://developers.google.com/speed/webp/" | |
}, { | |
"name": "Chromium blog - Chrome 32 Beta: Animated WebP images and faster Chrome for Android touch input", | |
"href": "https://blog.chromium.org/2013/11/chrome-32-beta-animated-webp-images-and.html" | |
}, { | |
"name": "Webp Lossless Spec", | |
"href": "https://developers.google.com/speed/webp/docs/webp_lossless_bitstream_specification" | |
}, { | |
"name": "Article about WebP support", | |
"href": "https://optimus.keycdn.com/support/webp-support/" | |
}, { | |
"name": "Chromium WebP announcement", | |
"href": "https://blog.chromium.org/2011/11/lossless-and-transparency-encoding-in.html?m=1" | |
}] | |
} | |
!*/ | |
/* DOC | |
Tests for lossy, non-alpha webp support. | |
Tests for all forms of webp support (lossless, lossy, alpha, and animated).. | |
Modernizr.webp // Basic support (lossy) | |
Modernizr.webp.lossless // Lossless | |
Modernizr.webp.alpha // Alpha (both lossy and lossless) | |
Modernizr.webp.animation // Animated WebP | |
*/ | |
Modernizr.addAsyncTest(function() { | |
var webpTests = [{ | |
'uri': 'data:image/webp;base64,UklGRiQAAABXRUJQVlA4IBgAAAAwAQCdASoBAAEAAwA0JaQAA3AA/vuUAAA=', | |
'name': 'webp' | |
}, { | |
'uri': 'data:image/webp;base64,UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAABBxAR/Q9ERP8DAABWUDggGAAAADABAJ0BKgEAAQADADQlpAADcAD++/1QAA==', | |
'name': 'webp.alpha' | |
}, { | |
'uri': 'data:image/webp;base64,UklGRlIAAABXRUJQVlA4WAoAAAASAAAAAAAAAAAAQU5JTQYAAAD/////AABBTk1GJgAAAAAAAAAAAAAAAAAAAGQAAABWUDhMDQAAAC8AAAAQBxAREYiI/gcA', | |
'name': 'webp.animation' | |
}, { | |
'uri': 'data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAAAAAAfQ//73v/+BiOh/AAA=', | |
'name': 'webp.lossless' | |
}]; | |
var webp = webpTests.shift(); | |
function test(name, uri, cb) { | |
var image = new Image(); | |
function addResult(event) { | |
// if the event is from 'onload', check the see if the image's width is | |
// 1 pixel (which indicates support). otherwise, it fails | |
var result = event && event.type === 'load' ? image.width === 1 : false; | |
var baseTest = name === 'webp'; | |
// if it is the base test, and the result is false, just set a literal false | |
// rather than use the Boolean constructor | |
addTest(name, (baseTest && result) ? new Boolean(result) : result); | |
if (cb) { | |
cb(event); | |
} | |
} | |
image.onerror = addResult; | |
image.onload = addResult; | |
image.src = uri; | |
} | |
// test for webp support in general | |
test(webp.name, webp.uri, function(e) { | |
// if the webp test loaded, test everything else. | |
if (e && e.type === 'load') { | |
for (var i = 0; i < webpTests.length; i++) { | |
test(webpTests[i].name, webpTests[i].uri); | |
} | |
} | |
}); | |
}); | |
/*! | |
{ | |
"name": "Webp Alpha", | |
"async": true, | |
"property": "webpalpha", | |
"aliases": ["webp-alpha"], | |
"tags": ["image"], | |
"authors": ["Krister Kari", "Rich Bradshaw", "Ryan Seddon", "Paul Irish"], | |
"notes": [{ | |
"name": "WebP Info", | |
"href": "https://developers.google.com/speed/webp/" | |
}, { | |
"name": "Article about WebP support", | |
"href": "https://optimus.keycdn.com/support/webp-support/" | |
}, { | |
"name": "Chromium WebP announcement", | |
"href": "https://blog.chromium.org/2011/11/lossless-and-transparency-encoding-in.html?m=1" | |
}] | |
} | |
!*/ | |
/* DOC | |
Tests for transparent webp support. | |
*/ | |
Modernizr.addAsyncTest(function() { | |
var image = new Image(); | |
image.onerror = function() { | |
addTest('webpalpha', false, {aliases: ['webp-alpha']}); | |
}; | |
image.onload = function() { | |
addTest('webpalpha', image.width === 1, {aliases: ['webp-alpha']}); | |
}; | |
image.src = 'data:image/webp;base64,UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAABBxAR/Q9ERP8DAABWUDggGAAAADABAJ0BKgEAAQADADQlpAADcAD++/1QAA=='; | |
}); | |
/*! | |
{ | |
"name": "IndexedDB", | |
"property": "indexeddb", | |
"caniuse": "indexeddb", | |
"tags": ["storage"], | |
"polyfills": ["indexeddb"], | |
"async": true | |
} | |
!*/ | |
/* DOC | |
Detects support for the IndexedDB client-side storage API (final spec). | |
*/ | |
// Vendors had inconsistent prefixing with the experimental Indexed DB: | |
// - Webkit's implementation is accessible through webkitIndexedDB | |
// - Firefox shipped moz_indexedDB before FF4b9, but since then has been mozIndexedDB | |
// For speed, we don't test the legacy (and beta-only) indexedDB | |
Modernizr.addAsyncTest(function() { | |
var indexeddb; | |
try { | |
// Firefox throws a Security Error when cookies are disabled | |
indexeddb = prefixed('indexedDB', window); | |
} catch (e) { | |
} | |
if (indexeddb) { | |
var testDBName = 'modernizr-' + Math.random(); | |
var req; | |
try { | |
req = indexeddb.open(testDBName); | |
} catch (e) { | |
addTest('indexeddb', false); | |
return; | |
} | |
req.onerror = function(event) { | |
if (req.error && (req.error.name === 'InvalidStateError' || req.error.name === 'UnknownError')) { | |
addTest('indexeddb', false); | |
event.preventDefault(); | |
} else { | |
addTest('indexeddb', true); | |
detectDeleteDatabase(indexeddb, testDBName); | |
} | |
}; | |
req.onsuccess = function() { | |
addTest('indexeddb', true); | |
detectDeleteDatabase(indexeddb, testDBName); | |
}; | |
} else { | |
addTest('indexeddb', false); | |
} | |
}); | |
function detectDeleteDatabase(indexeddb, testDBName) { | |
var deleteReq = indexeddb.deleteDatabase(testDBName); | |
deleteReq.onsuccess = function() { | |
addTest('indexeddb.deletedatabase', true); | |
}; | |
deleteReq.onerror = function() { | |
addTest('indexeddb.deletedatabase', false); | |
}; | |
} | |
; | |
/*! | |
{ | |
"name": "JSON", | |
"property": "json", | |
"caniuse": "json", | |
"notes": [{ | |
"name": "MDN Docs", | |
"href": "https://developer.mozilla.org/en-US/docs/Glossary/JSON" | |
}], | |
"polyfills": ["json2"] | |
} | |
!*/ | |
/* DOC | |
Detects native support for JSON handling functions. | |
*/ | |
// this will also succeed if you've loaded the JSON2.js polyfill ahead of time | |
// ... but that should be obvious. :) | |
Modernizr.addTest('json', 'JSON' in window && 'parse' in JSON && 'stringify' in JSON); | |
/*! | |
{ | |
"name": "Fetch API", | |
"property": "fetch", | |
"tags": ["network"], | |
"caniuse": "fetch", | |
"notes": [{ | |
"name": "WHATWG Spec", | |
"href": "https://fetch.spec.whatwg.org/" | |
}], | |
"polyfills": ["fetch"] | |
} | |
!*/ | |
/* DOC | |
Detects support for the fetch API, a modern replacement for XMLHttpRequest. | |
*/ | |
Modernizr.addTest('fetch', 'fetch' in window); | |
/*! | |
{ | |
"name": "XML HTTP Request Level 2 XHR2", | |
"property": "xhr2", | |
"caniuse": "xhr2", | |
"tags": ["network"], | |
"builderAliases": ["network_xhr2"], | |
"notes": [{ | |
"name": "W3C Spec", | |
"href": "https://www.w3.org/TR/XMLHttpRequest2/" | |
}, { | |
"name": "Details on Related Github Issue", | |
"href": "https://github.com/Modernizr/Modernizr/issues/385" | |
}] | |
} | |
!*/ | |
/* DOC | |
Tests for XHR2. | |
*/ | |
// all three of these details report consistently across all target browsers: | |
// !!(window.ProgressEvent); | |
// 'XMLHttpRequest' in window && 'withCredentials' in new XMLHttpRequest | |
Modernizr.addTest('xhr2', 'XMLHttpRequest' in window && 'withCredentials' in new XMLHttpRequest()); | |
/*! | |
{ | |
"name": "Notification", | |
"property": "notification", | |
"caniuse": "notifications", | |
"authors": ["Theodoor van Donge", "Hendrik Beskow"], | |
"notes": [{ | |
"name": "HTML5 Rocks Tutorial", | |
"href": "https://www.html5rocks.com/en/tutorials/notifications/quick/" | |
}, { | |
"name": "W3C Spec", | |
"href": "https://www.w3.org/TR/notifications/" | |
}, { | |
"name": "Changes in Chrome to Notifications API due to Service Worker Push Notifications", | |
"href": "https://developers.google.com/web/updates/2015/05/Notifying-you-of-notificiation-changes" | |
}], | |
"knownBugs": ["Possibility of false-positive on Chrome for Android if permissions we're granted for a website prior to Chrome 44."], | |
"polyfills": ["desktop-notify", "html5-notifications"] | |
} | |
!*/ | |
/* DOC | |
Detects support for the Notifications API | |
*/ | |
Modernizr.addTest('notification', function() { | |
if (!window.Notification || !window.Notification.requestPermission) { | |
return false; | |
} | |
// if permission is already granted, assume support | |
if (window.Notification.permission === 'granted') { | |
return true; | |
} | |
try { | |
new window.Notification(''); | |
} catch (e) { | |
if (e.name === 'TypeError') { | |
return false; | |
} | |
} | |
return true; | |
}); | |
/*! | |
{ | |
"name": "Navigation Timing API", | |
"property": "performance", | |
"caniuse": "nav-timing", | |
"tags": ["performance"], | |
"authors": ["Scott Murphy (@uxder)"], | |
"notes": [{ | |
"name": "W3C Spec", | |
"href": "https://www.w3.org/TR/navigation-timing/" | |
}, { | |
"name": "HTML5 Rocks Tutorial", | |
"href": "https://www.html5rocks.com/en/tutorials/webperformance/basics/" | |
}], | |
"polyfills": ["perfnow"] | |
} | |
!*/ | |
/* DOC | |
Detects support for the Navigation Timing API, for measuring browser and connection performance. | |
*/ | |
Modernizr.addTest('performance', !!prefixed('performance', window)); | |
/** | |
* List of JavaScript DOM values used for tests including a NON-prefix | |
* | |
* @memberOf Modernizr | |
* @name Modernizr._domPrefixesAll | |
* @optionName Modernizr._domPrefixesAll | |
* @optionProp domPrefixesAll | |
* @access public | |
* @example | |
* | |
* Modernizr._domPrefixesAll is exactly the same as [_domPrefixes](#modernizr-_domPrefixes), but also | |
* adds an empty string in the array to test for a non-prefixed value | |
* | |
* ```js | |
* Modernizr._domPrefixesAll === [ "", "Moz", "O", "ms", "Webkit" ]; | |
* ``` | |
*/ | |
var domPrefixesAll = [''].concat(domPrefixes); | |
ModernizrProto._domPrefixesAll = domPrefixesAll; | |
/*! | |
{ | |
"name": "DOM Pointer Events API", | |
"property": "pointerevents", | |
"caniuse": "pointer", | |
"tags": ["input"], | |
"authors": ["Stu Cox"], | |
"notes": [{ | |
"name": "W3C Spec (Pointer Events)", | |
"href": "https://www.w3.org/TR/pointerevents/" | |
}, { | |
"name": "W3C Spec (Pointer Events Level 2)", | |
"href": "https://www.w3.org/TR/pointerevents2/" | |
}, { | |
"name": "MDN Docs", | |
"href": "https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent" | |
}], | |
"warnings": ["This property name now refers to W3C DOM PointerEvents: https://github.com/Modernizr/Modernizr/issues/548#issuecomment-12812099"], | |
"polyfills": ["pep"] | |
} | |
!*/ | |
/* DOC | |
Detects support for the DOM Pointer Events API, which provides a unified event interface for pointing input devices, as implemented in IE10+, Edge and Blink. | |
*/ | |
// **Test name hijacked!** | |
// Now refers to W3C DOM PointerEvents spec rather than the CSS pointer-events property. | |
Modernizr.addTest('pointerevents', function() { | |
// Cannot use `.prefixed()` for events, so test each prefix | |
for (var i = 0, len = domPrefixesAll.length; i < len; i++) { | |
if (hasEvent(domPrefixesAll[i] + 'pointerdown')) { | |
return true; | |
} | |
} | |
return false; | |
}); | |
/*! | |
{ | |
"name": "QuerySelector", | |
"property": "queryselector", | |
"caniuse": "queryselector", | |
"tags": ["queryselector"], | |
"authors": ["Andrew Betts (@triblondon)"], | |
"notes": [{ | |
"name": "W3C Spec", | |
"href": "https://www.w3.org/TR/selectors-api/#queryselectorall" | |
}], | |
"polyfills": ["css-selector-engine"] | |
} | |
!*/ | |
/* DOC | |
Detects support for querySelector. | |
*/ | |
Modernizr.addTest('queryselector', 'querySelector' in document && 'querySelectorAll' in document); | |
/*! | |
{ | |
"name": "script[async]", | |
"property": "scriptasync", | |
"caniuse": "script-async", | |
"tags": ["script"], | |
"builderAliases": ["script_async"], | |
"authors": ["Theodoor van Donge"] | |
} | |
!*/ | |
/* DOC | |
Detects support for the `async` attribute on the `<script>` element. | |
*/ | |
Modernizr.addTest('scriptasync', 'async' in createElement('script')); | |
/*! | |
{ | |
"name": "script[defer]", | |
"property": "scriptdefer", | |
"caniuse": "script-defer", | |
"tags": ["script"], | |
"builderAliases": ["script_defer"], | |
"authors": ["Theodoor van Donge"], | |
"warnings": ["Browser implementation of the `defer` attribute vary: https://stackoverflow.com/questions/3952009/defer-attribute-chrome#answer-3982619"], | |
"knownBugs": ["False positive in Opera 12"] | |
} | |
!*/ | |
/* DOC | |
Detects support for the `defer` attribute on the `<script>` element. | |
*/ | |
Modernizr.addTest('scriptdefer', 'defer' in createElement('script')); | |
/*! | |
{ | |
"name": "ServiceWorker API", | |
"property": "serviceworker", | |
"caniuse": "serviceworkers", | |
"notes": [{ | |
"name": "ServiceWorkers Explained", | |
"href": "https://github.com/slightlyoff/ServiceWorker/blob/master/explainer.md" | |
}] | |
} | |
!*/ | |
/* DOC | |
ServiceWorkers (formerly Navigation Controllers) are a way to persistently cache resources to built apps that work better offline. | |
*/ | |
Modernizr.addTest('serviceworker', 'serviceWorker' in navigator); | |
/*! | |
{ | |
"name": "Local Storage", | |
"property": "localstorage", | |
"caniuse": "namevalue-storage", | |
"tags": ["storage"], | |
"polyfills": [ | |
"joshuabell-polyfill", | |
"cupcake", | |
"storagepolyfill", | |
"amplifyjs", | |
"yui-cacheoffline" | |
] | |
} | |
!*/ | |
// In FF4, if disabled, window.localStorage should === null. | |
// Normally, we could not test that directly and need to do a | |
// `('localStorage' in window)` test first because otherwise Firefox will | |
// throw bugzil.la/365772 if cookies are disabled | |
// Similarly, in Chrome with "Block third-party cookies and site data" enabled, | |
// attempting to access `window.sessionStorage` will throw an exception. crbug.com/357625 | |
// Also in iOS5 Private Browsing mode, attempting to use localStorage.setItem | |
// will throw the exception: | |
// QUOTA_EXCEEDED_ERROR DOM Exception 22. | |
// Peculiarly, getItem and removeItem calls do not throw. | |
// Because we are forced to try/catch this, we'll go aggressive. | |
// Just FWIW: IE8 Compat mode supports these features completely: | |
// www.quirksmode.org/dom/html5.html | |
// But IE8 doesn't support either with local files | |
Modernizr.addTest('localstorage', function() { | |
var mod = 'modernizr'; | |
try { | |
localStorage.setItem(mod, mod); | |
localStorage.removeItem(mod); | |
return true; | |
} catch (e) { | |
return false; | |
} | |
}); | |
/*! | |
{ | |
"name": "Session Storage", | |
"property": "sessionstorage", | |
"tags": ["storage"], | |
"polyfills": ["joshuabell-polyfill", "cupcake", "sessionstorage"] | |
} | |
!*/ | |
// Because we are forced to try/catch this, we'll go aggressive. | |
// Just FWIW: IE8 Compat mode supports these features completely: | |
// www.quirksmode.org/dom/html5.html | |
// But IE8 doesn't support either with local files | |
Modernizr.addTest('sessionstorage', function() { | |
var mod = 'modernizr'; | |
try { | |
sessionStorage.setItem(mod, mod); | |
sessionStorage.removeItem(mod); | |
return true; | |
} catch (e) { | |
return false; | |
} | |
}); | |
/*! | |
{ | |
"name": "Web SQL Database", | |
"property": "websqldatabase", | |
"caniuse": "sql-storage", | |
"tags": ["storage"] | |
} | |
!*/ | |
// Chrome incognito mode used to throw an exception when using openDatabase | |
// It doesn't anymore. | |
Modernizr.addTest('websqldatabase', 'openDatabase' in window); | |
/*! | |
{ | |
"name": "style[scoped]", | |
"property": "stylescoped", | |
"caniuse": "style-scoped", | |
"tags": ["dom"], | |
"builderAliases": ["style_scoped"], | |
"authors": ["Cătălin Mariș"], | |
"notes": [{ | |
"name": "WHATWG Spec", | |
"href": "https://html.spec.whatwg.org/multipage/semantics.html#attr-style-scoped" | |
}], | |
"polyfills": ["scoped-styles"] | |
} | |
!*/ | |
/* DOC | |
Support for the `scoped` attribute of the `<style>` element. | |
*/ | |
Modernizr.addTest('stylescoped', 'scoped' in createElement('style')); | |
/*! | |
{ | |
"name": "SVG", | |
"property": "svg", | |
"caniuse": "svg", | |
"tags": ["svg"], | |
"authors": ["Erik Dahlstrom"], | |
"polyfills": [ | |
"svgweb", | |
"raphael", | |
"amplesdk", | |
"canvg", | |
"svg-boilerplate", | |
"sie", | |
"dojogfx", | |
"fabricjs" | |
] | |
} | |
!*/ | |
/* DOC | |
Detects support for SVG in `<embed>` or `<object>` elements. | |
*/ | |
Modernizr.addTest('svg', !!document.createElementNS && !!document.createElementNS('http://www.w3.org/2000/svg', 'svg').createSVGRect); | |
/*! | |
{ | |
"name": "Template strings", | |
"property": "templatestrings", | |
"notes": [{ | |
"name": "MDN Docs", | |
"href": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Browser_compatibility" | |
}] | |
} | |
!*/ | |
/* DOC | |
Template strings are string literals allowing embedded expressions. | |
*/ | |
Modernizr.addTest('templatestrings', function() { | |
var supports; | |
try { | |
// A number of tools, including uglifyjs and require, break on a raw "`", so | |
// use an eval to get around that. | |
// eslint-disable-next-line | |
eval('``'); | |
supports = true; | |
} catch (e) {} | |
return !!supports; | |
}); | |
/** | |
* Modernizr.mq tests a given media query, live against the current state of the window | |
* adapted from matchMedia polyfill by Scott Jehl and Paul Irish | |
* gist.github.com/786768 | |
* | |
* @memberOf Modernizr | |
* @name Modernizr.mq | |
* @optionName Modernizr.mq() | |
* @optionProp mq | |
* @access public | |
* @function mq | |
* @param {string} mq - String of the media query we want to test | |
* @returns {boolean} | |
* @example | |
* Modernizr.mq allows for you to programmatically check if the current browser | |
* window state matches a media query. | |
* | |
* ```js | |
* var query = Modernizr.mq('(min-width: 900px)'); | |
* | |
* if (query) { | |
* // the browser window is larger than 900px | |
* } | |
* ``` | |
* | |
* Only valid media queries are supported, therefore you must always include values | |
* with your media query | |
* | |
* ```js | |
* // good | |
* Modernizr.mq('(min-width: 900px)'); | |
* | |
* // bad | |
* Modernizr.mq('min-width'); | |
* ``` | |
* | |
* If you would just like to test that media queries are supported in general, use | |
* | |
* ```js | |
* Modernizr.mq('only all'); // true if MQ are supported, false if not | |
* ``` | |
* | |
* Note that if the browser does not support media queries (e.g. old IE) mq will | |
* always return false. | |
*/ | |
var mq = (function() { | |
var matchMedia = window.matchMedia || window.msMatchMedia; | |
if (matchMedia) { | |
return function(mq) { | |
var mql = matchMedia(mq); | |
return mql && mql.matches || false; | |
}; | |
} | |
return function(mq) { | |
var bool = false; | |
injectElementWithStyles('@media ' + mq + ' { #modernizr { position: absolute; } }', function(node) { | |
bool = computedStyle(node, null, 'position') === 'absolute'; | |
}); | |
return bool; | |
}; | |
})(); | |
ModernizrProto.mq = mq; | |
/*! | |
{ | |
"name": "Touch Events", | |
"property": "touchevents", | |
"caniuse": "touch", | |
"tags": ["media", "attribute"], | |
"notes": [{ | |
"name": "Touch Events spec", | |
"href": "https://www.w3.org/TR/2013/WD-touch-events-20130124/" | |
}], | |
"warnings": [ | |
"** DEPRECATED see https://github.com/Modernizr/Modernizr/pull/2432 **", | |
"Indicates if the browser supports the Touch Events spec, and does not necessarily reflect a touchscreen device" | |
], | |
"knownBugs": [ | |
"False-positive on some configurations of Nokia N900", | |
"False-positive on some BlackBerry 6.0 builds – https://github.com/Modernizr/Modernizr/issues/372#issuecomment-3112695" | |
] | |
} | |
!*/ | |
/* DOC | |
Indicates if the browser supports the W3C Touch Events API. | |
This *does not* necessarily reflect a touchscreen device: | |
* Older touchscreen devices only emulate mouse events | |
* Modern IE touch devices implement the Pointer Events API instead: use `Modernizr.pointerevents` to detect support for that | |
* Some browsers & OS setups may enable touch APIs when no touchscreen is connected | |
* Future browsers may implement other event models for touch interactions | |
See this article: [You Can't Detect A Touchscreen](http://www.stucox.com/blog/you-cant-detect-a-touchscreen/). | |
It's recommended to bind both mouse and touch/pointer events simultaneously – see [this HTML5 Rocks tutorial](https://www.html5rocks.com/en/mobile/touchandmouse/). | |
This test will also return `true` for Firefox 4 Multitouch support. | |
*/ | |
// Chrome (desktop) used to lie about its support on this, but that has since been rectified: https://bugs.chromium.org/p/chromium/issues/detail?id=36415 | |
// Chrome also changed its behaviour since v70 and recommends the TouchEvent object for detection: https://www.chromestatus.com/feature/4764225348042752 | |
Modernizr.addTest('touchevents', function() { | |
if (('ontouchstart' in window) || window.TouchEvent || window.DocumentTouch && document instanceof DocumentTouch) { | |
return true; | |
} | |
// include the 'heartz' as a way to have a non matching MQ to help terminate the join | |
// https://github.com/Modernizr/Modernizr/issues/1814 | |
var query = ['(', prefixes.join('touch-enabled),('), 'heartz', ')'].join(''); | |
return mq(query); | |
}); | |
/*! | |
{ | |
"name": "Typed arrays", | |
"property": "typedarrays", | |
"caniuse": "typedarrays", | |
"tags": ["js"], | |
"authors": ["Stanley Stuart (@fivetanley)"], | |
"notes": [{ | |
"name": "MDN Docs", | |
"href": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays" | |
}, { | |
"name": "Kronos spec", | |
"href": "http://www.ecma-international.org/ecma-262/6.0/#sec-typedarray-objects" | |
}], | |
"polyfills": ["joshuabell-polyfill"] | |
} | |
!*/ | |
/* DOC | |
Detects support for native binary data manipulation via Typed Arrays in JavaScript. | |
Does not check for DataView support; use `Modernizr.dataview` for that. | |
*/ | |
// Should fail in: | |
// Internet Explorer <= 9 | |
// Firefox <= 3.6 | |
// Chrome <= 6.0 | |
// iOS Safari < 4.2 | |
// Safari < 5.1 | |
// Opera < 11.6 | |
// Opera Mini, <= 7.0 | |
// Android Browser < 4.0 | |
// Blackberry Browser < 10.0 | |
Modernizr.addTest('typedarrays', 'ArrayBuffer' in window); | |
/*! | |
{ | |
"name": "Blob URLs", | |
"property": "bloburls", | |
"caniuse": "bloburls", | |
"notes": [{ | |
"name": "W3C Working Draft Spec", | |
"href": "https://www.w3.org/TR/FileAPI/#creating-revoking" | |
}], | |
"tags": ["file", "url"], | |
"authors": ["Ron Waldon (@jokeyrhyme)"] | |
} | |
!*/ | |
/* DOC | |
Detects support for creating Blob URLs | |
*/ | |
var url = prefixed('URL', window, false); | |
url = url && window[url]; | |
Modernizr.addTest('bloburls', url && 'revokeObjectURL' in url && 'createObjectURL' in url); | |
/*! | |
{ | |
"name": "Data URI", | |
"property": "datauri", | |
"caniuse": "datauri", | |
"tags": ["url"], | |
"builderAliases": ["url_data_uri"], | |
"async": true, | |
"notes": [{ | |
"name": "Wikipedia article", | |
"href": "https://en.wikipedia.org/wiki/Data_URI_scheme" | |
}], | |
"warnings": ["Support in Internet Explorer 8 is limited to images and linked resources like CSS files, not HTML files"] | |
} | |
!*/ | |
/* DOC | |
Detects support for data URIs. Provides a subproperty to report support for data URIs over 32kb in size: | |
```javascript | |
Modernizr.datauri // true | |
Modernizr.datauri.over32kb // false in IE8 | |
``` | |
*/ | |
// https://github.com/Modernizr/Modernizr/issues/14 | |
Modernizr.addAsyncTest(function() { | |
// IE7 throw a mixed content warning on HTTPS for this test, so we'll | |
// just reject it (we know it doesn't support data URIs anyway) | |
// https://github.com/Modernizr/Modernizr/issues/362 | |
if (navigator.userAgent.indexOf('MSIE 7.') !== -1) { | |
// Keep the test async | |
setTimeout(function() { | |
Modernizr.addTest('datauri', new Boolean(false)); | |
}, 10); | |
} | |
var datauri = new Image(); | |
datauri.onerror = function() { | |
Modernizr.addTest('datauri', new Boolean(false)); | |
}; | |
datauri.onload = function() { | |
if (datauri.width === 1 && datauri.height === 1) { | |
testOver32kb(); | |
} | |
else { | |
Modernizr.addTest('datauri', new Boolean(false)); | |
} | |
}; | |
datauri.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=='; | |
// Once we have datauri, let's check to see if we can use data URIs over | |
// 32kb (IE8 can't). https://github.com/Modernizr/Modernizr/issues/321 | |
function testOver32kb() { | |
var datauriBig = new Image(); | |
datauriBig.onerror = function() { | |
Modernizr.addTest('datauri', new Boolean(true)); | |
Modernizr.addTest('datauri.over32kb', false); | |
}; | |
datauriBig.onload = function() { | |
Modernizr.addTest('datauri', new Boolean(true)); | |
Modernizr.addTest('datauri.over32kb', datauriBig.width === 1 && datauriBig.height === 1); | |
}; | |
var base64str = 'R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=='; | |
while (base64str.length < 33000) { | |
base64str = '\r\n' + base64str; | |
} | |
datauriBig.src = 'data:image/gif;base64,' + base64str; | |
} | |
}); | |
/*! | |
{ | |
"name": "HTML5 Video", | |
"property": "video", | |
"caniuse": "video", | |
"tags": ["html5", "video", "media"], | |
"knownBugs": ["Without QuickTime, `Modernizr.video.h264` will be `undefined`; https://github.com/Modernizr/Modernizr/issues/546"], | |
"polyfills": [ | |
"html5media", | |
"mediaelementjs", | |
"sublimevideo", | |
"videojs", | |
"leanbackplayer", | |
"videoforeverybody" | |
] | |
} | |
!*/ | |
/* DOC | |
Detects support for the video element, as well as testing what types of content it supports. | |
Subproperties are provided to describe support for `ogg`, `h264`, `h265`, `webm`, `vp9`, `hls` and `av1` formats, e.g.: | |
```javascript | |
Modernizr.video // true | |
Modernizr.video.ogg // 'probably' | |
``` | |
*/ | |
// Codec values from : github.com/NielsLeenheer/html5test/blob/9106a8/index.html#L845 | |
// thx to NielsLeenheer and zcorpan | |
// Note: in some older browsers, "no" was a return value instead of empty string. | |
// It was live in FF3.5.0 and 3.5.1, but fixed in 3.5.2 | |
// It was also live in Safari 4.0.0 - 4.0.4, but fixed in 4.0.5 | |
(function() { | |
var elem = createElement('video'); | |
Modernizr.addTest('video', function() { | |
var bool = false; | |
try { | |
bool = !!elem.canPlayType; | |
if (bool) { | |
bool = new Boolean(bool); | |
} | |
} catch (e) {} | |
return bool; | |
}); | |
// IE9 Running on Windows Server SKU can cause an exception to be thrown, bug #224 | |
try { | |
if (!!elem.canPlayType) { | |
Modernizr.addTest('video.ogg', elem.canPlayType('video/ogg; codecs="theora"').replace(/^no$/, '')); | |
// Without QuickTime, this value will be `undefined`. github.com/Modernizr/Modernizr/issues/546 | |
Modernizr.addTest('video.h264', elem.canPlayType('video/mp4; codecs="avc1.42E01E"').replace(/^no$/, '')); | |
Modernizr.addTest('video.h265', elem.canPlayType('video/mp4; codecs="hev1"').replace(/^no$/, '')); | |
Modernizr.addTest('video.webm', elem.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/, '')); | |
Modernizr.addTest('video.vp9', elem.canPlayType('video/webm; codecs="vp9"').replace(/^no$/, '')); | |
Modernizr.addTest('video.hls', elem.canPlayType('application/x-mpegURL; codecs="avc1.42E01E"').replace(/^no$/, '')); | |
Modernizr.addTest('video.av1', elem.canPlayType('video/mp4; codecs="av01"').replace(/^no$/, '')); | |
} | |
} catch (e) {} | |
})(); | |
/*! | |
{ | |
"name": "WebGL", | |
"property": "webgl", | |
"caniuse": "webgl", | |
"tags": ["webgl", "graphics"], | |
"polyfills": ["jebgl", "cwebgl", "iewebgl"] | |
} | |
!*/ | |
Modernizr.addTest('webgl', function() { | |
return 'WebGLRenderingContext' in window; | |
}); | |
/*! | |
{ | |
"name": "WebSockets Support", | |
"property": "websockets", | |
"authors": ["Phread (@fearphage)", "Mike Sherov (@mikesherov)", "Burak Yigit Kaya (@BYK)"], | |
"caniuse": "websockets", | |
"tags": ["html5"], | |
"warnings": [ | |
"This test will reject any old version of WebSockets even if it is not prefixed such as in Safari 5.1" | |
], | |
"notes": [{ | |
"name": "CLOSING State and Spec", | |
"href": "https://www.w3.org/TR/websockets/#the-websocket-interface" | |
}], | |
"polyfills": [ | |
"sockjs", | |
"socketio", | |
"kaazing-websocket-gateway", | |
"websocketjs", | |
"atmosphere", | |
"graceful-websocket", | |
"portal", | |
"datachannel" | |
] | |
} | |
!*/ | |
var supports = false; | |
try { | |
supports = 'WebSocket' in window && window.WebSocket.CLOSING === 2; | |
} catch (e) {} | |
Modernizr.addTest('websockets', supports); | |
// Run each test | |
Modernizr.testRunner = testRunner; | |
delete ModernizrProto.addTest; | |
delete ModernizrProto.addAsyncTest; | |
// Run the things that are supposed to run after the tests | |
for (var i = 0; i < Modernizr._q.length; i++) { | |
Modernizr._q[i](); | |
} | |
// Leak Modernizr namespace | |
scriptGlobalObject.Modernizr = Modernizr; | |
; | |
})(window, window, document); | |
module.exports = Modernizr; |