/*! * modernizr v3.3.1 * Build https://modernizr.com/download?-backgroundsize-borderimage-borderradius-boxshadow-cssanimations-csstransforms-csstransforms3d-csstransitions-fontface-generatedcontent-inlinesvg-opacity-rgba-textshadow-touchevents-domprefixes-mq-prefixes-setclasses-shiv-testallprops-testprop-teststyles-dontmin * * Copyright (c) * Faruk Ates * Paul Irish * Alex Sexton * Ryan Seddon * Patrick Kettner * Stu Cox * Richard Herrera * 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 `` element. This * information allows you to progressively enhance your pages with a granular level * of control over the experience. */ ;(function(window, document, undefined){ var classes = []; var tests = []; /** * * ModernizrProto is the constructor for Modernizr * * @class * @access public */ var ModernizrProto = { // The current version, dummy _version: '3.3.1', // Any settings that don't work as separate modules // can go in here as configuration. _config: { 'classPrefix': '', 'enableClasses': true, '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(); /** * List of property values to set for css tests. See ticket #21 * http://git.io/vUGl4 * * @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 kebab-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; /** * 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} */ function is(obj, type) { return typeof obj === type; } ; /** * Run through all tests and detect their support in the current UA. * * @access private */ 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 /* jshint -W053 */ if (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'; /** * 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 className += ' ' + classPrefix + classes.join(' ' + classPrefix); isSVG ? docElement.className.baseVal = className : docElement.className = className; } } ; /** * @optionName html5shiv * @optionProp html5shiv */ // Take the html5 variable out of the html5shiv scope so we can return it. var html5; if (!isSVG) { /** * @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed */ ;(function(window, document) { /*jshint evil:true */ /** version */ var version = '3.7.3'; /** Preset options */ var options = window.html5 || {}; /** Used to skip problem elements */ var reSkip = /^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i; /** Not all elements can be cloned in IE **/ var saveClones = /^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i; /** Detect whether the browser supports default html5 styles */ var supportsHtml5Styles; /** Name of the expando, to work with multiple documents or to re-shiv one document */ var expando = '_html5shiv'; /** The id for the the documents expando */ var expanID = 0; /** Cached data for each document */ var expandoData = {}; /** Detect whether the browser supports unknown elements */ var supportsUnknownElements; (function() { try { var a = document.createElement('a'); a.innerHTML = ''; //if the hidden property is implemented we can assume, that the browser supports basic HTML5 Styles supportsHtml5Styles = ('hidden' in a); supportsUnknownElements = a.childNodes.length == 1 || (function() { // assign a false positive if unable to shiv (document.createElement)('a'); var frag = document.createDocumentFragment(); return ( typeof frag.cloneNode == 'undefined' || typeof frag.createDocumentFragment == 'undefined' || typeof frag.createElement == 'undefined' ); }()); } catch(e) { // assign a false positive if detection fails => unable to shiv supportsHtml5Styles = true; supportsUnknownElements = true; } }()); /*--------------------------------------------------------------------------*/ /** * Creates a style sheet with the given CSS text and adds it to the document. * @private * @param {Document} ownerDocument The document. * @param {String} cssText The CSS text. * @returns {StyleSheet} The style element. */ function addStyleSheet(ownerDocument, cssText) { var p = ownerDocument.createElement('p'), parent = ownerDocument.getElementsByTagName('head')[0] || ownerDocument.documentElement; p.innerHTML = 'x'; return parent.insertBefore(p.lastChild, parent.firstChild); } /** * Returns the value of `html5.elements` as an array. * @private * @returns {Array} An array of shived element node names. */ function getElements() { var elements = html5.elements; return typeof elements == 'string' ? elements.split(' ') : elements; } /** * Extends the built-in list of html5 elements * @memberOf html5 * @param {String|Array} newElements whitespace separated list or array of new element names to shiv * @param {Document} ownerDocument The context document. */ function addElements(newElements, ownerDocument) { var elements = html5.elements; if(typeof elements != 'string'){ elements = elements.join(' '); } if(typeof newElements != 'string'){ newElements = newElements.join(' '); } html5.elements = elements +' '+ newElements; shivDocument(ownerDocument); } /** * Returns the data associated to the given document * @private * @param {Document} ownerDocument The document. * @returns {Object} An object of data. */ function getExpandoData(ownerDocument) { var data = expandoData[ownerDocument[expando]]; if (!data) { data = {}; expanID++; ownerDocument[expando] = expanID; expandoData[expanID] = data; } return data; } /** * returns a shived element for the given nodeName and document * @memberOf html5 * @param {String} nodeName name of the element * @param {Document|DocumentFragment} ownerDocument The context document. * @returns {Object} The shived element. */ function createElement(nodeName, ownerDocument, data){ if (!ownerDocument) { ownerDocument = document; } if(supportsUnknownElements){ return ownerDocument.createElement(nodeName); } if (!data) { data = getExpandoData(ownerDocument); } var node; if (data.cache[nodeName]) { node = data.cache[nodeName].cloneNode(); } else if (saveClones.test(nodeName)) { node = (data.cache[nodeName] = data.createElem(nodeName)).cloneNode(); } else { node = data.createElem(nodeName); } // Avoid adding some elements to fragments in IE < 9 because // * Attributes like `name` or `type` cannot be set/changed once an element // is inserted into a document/fragment // * Link elements with `src` attributes that are inaccessible, as with // a 403 response, will cause the tab/window to crash // * Script elements appended to fragments will execute when their `src` // or `text` property is set return node.canHaveChildren && !reSkip.test(nodeName) && !node.tagUrn ? data.frag.appendChild(node) : node; } /** * returns a shived DocumentFragment for the given document * @memberOf html5 * @param {Document} ownerDocument The context document. * @returns {Object} The shived DocumentFragment. */ function createDocumentFragment(ownerDocument, data){ if (!ownerDocument) { ownerDocument = document; } if(supportsUnknownElements){ return ownerDocument.createDocumentFragment(); } data = data || getExpandoData(ownerDocument); var clone = data.frag.cloneNode(), i = 0, elems = getElements(), l = elems.length; for(;i -1; }); /*! { "name": "Inline SVG", "property": "inlinesvg", "caniuse": "svg-html5", "tags": ["svg"], "notes": [{ "name": "Test page", "href": "https://paulirish.com/demo/inline-svg" }, { "name": "Test page and results", "href": "https://codepen.io/eltonmesquita/full/GgXbvo/" }], "polyfills": ["inline-svg-polyfill"], "knownBugs": ["False negative on some Chromia browsers."] } !*/ /* DOC Detects support for inline SVG in HTML (not within XHTML). */ Modernizr.addTest('inlinesvg', function() { var div = createElement('div'); div.innerHTML = ''; return (typeof SVGRect != 'undefined' && div.firstChild && div.firstChild.namespaceURI) == 'http://www.w3.org/2000/svg'; }); /*! { "name": "CSS Supports", "property": "supports", "caniuse": "css-featurequeries", "tags": ["css"], "builderAliases": ["css_supports"], "notes": [{ "name": "W3 Spec", "href": "http://dev.w3.org/csswg/css3-conditional/#at-supports" },{ "name": "Related Github Issue", "href": "github.com/Modernizr/Modernizr/issues/648" },{ "name": "W3 Info", "href": "http://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); /** * 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} */ 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.removeChild(body); docElement.style.overflow = docOverflow; // Trigger layout so kinetic scrolling isn't disabled in iOS6+ docElement.offsetHeight; } else { div.parentNode.removeChild(div); } return !!ret; } ; /** * 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 = (window.getComputedStyle ? window.getComputedStyle(node, null) : node.currentStyle).position == 'absolute'; }); return bool; }; })(); ModernizrProto.mq = mq; /** * testStyles injects an element with style element and some CSS rules * * @memberof Modernizr * @name Modernizr.testStyles * @optionName Modernizr.testStyles() * @optionProp testStyles * @access public * @function testStyles * @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} * @example * * `Modernizr.testStyles` takes a CSS rule and injects it onto the current page * along with (possibly multiple) DOM elements. This lets you check for features * that can not be detected by simply checking the [IDL](https://developer.mozilla.org/en-US/docs/Mozilla/Developer_guide/Interface_development_guide/IDL_interface_rules). * * ```js * Modernizr.testStyles('#modernizr { width: 9px; color: papayawhip; }', function(elem, rule) { * // elem is the first DOM node in the page (by default #modernizr) * // rule is the first argument you supplied - the CSS rule in string form * * addTest('widthworks', elem.style.width === '9px') * }); * ``` * * If your test requires multiple nodes, you can include a third argument * indicating how many additional div elements to include on the page. The * additional nodes are injected as children of the `elem` that is returned as * the first argument to the callback. * * ```js * Modernizr.testStyles('#modernizr {width: 1px}; #modernizr2 {width: 2px}', function(elem) { * document.getElementById('modernizr').style.width === '1px'; // true * document.getElementById('modernizr2').style.width === '2px'; // true * elem.firstChild === document.getElementById('modernizr2'); // true * }, 1); * ``` * * By default, all of the additional elements have an ID of `modernizr[n]`, where * `n` is its index (e.g. the first additional, second overall is `#modernizr2`, * the second additional is `#modernizr3`, etc.). * If you want to have more meaningful IDs for your function, you can provide * them as the fourth argument, as an array of strings * * ```js * Modernizr.testStyles('#foo {width: 10px}; #bar {height: 20px}', function(elem) { * elem.firstChild === document.getElementById('foo'); // true * elem.lastChild === document.getElementById('bar'); // true * }, 2, ['foo', 'bar']); * ``` * */ var testStyles = ModernizrProto.testStyles = injectElementWithStyles; /*! { "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": [ "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](http://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: http://crbug.com/36415 Modernizr.addTest('touchevents', function() { var bool; if (('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) { bool = true; } else { // include the 'heartz' as a way to have a non matching MQ to help terminate the join // https://git.io/vznFH var query = ['@media (', prefixes.join('touch-enabled),('), 'heartz', ')', '{#modernizr{top:9px;position:absolute}}'].join(''); testStyles(query, function(node) { bool = node.offsetTop === 9; }); } return bool; }); /*! { "name": "@font-face", "property": "fontface", "authors": ["Diego Perini", "Mat Marquis"], "tags": ["css"], "knownBugs": [ "False Positive: WebOS https://github.com/Modernizr/Modernizr/issues/342", "False Postive: WP7 https://github.com/Modernizr/Modernizr/issues/538" ], "notes": [{ "name": "@font-face detection routine by Diego Perini", "href": "http://javascript.nwbox.com/CSSSupport/" },{ "name": "Filament Group @font-face compatibility research", "href": "https://docs.google.com/presentation/d/1n4NyG4uPRjAA8zn_pSQ_Ket0RhcWC6QlZ6LMjKeECo0/edit#slide=id.p" },{ "name": "Filament Grunticon/@font-face device testing results", "href": "https://docs.google.com/spreadsheet/ccc?key=0Ag5_yGvxpINRdHFYeUJPNnZMWUZKR2ItMEpRTXZPdUE#gid=0" },{ "name": "CSS fonts on Android", "href": "https://stackoverflow.com/questions/3200069/css-fonts-on-android" },{ "name": "@font-face and Android", "href": "http://archivist.incutio.com/viewlist/css-discuss/115960" }] } !*/ var blacklist = (function() { var ua = navigator.userAgent; var wkvers = ua.match(/applewebkit\/([0-9]+)/gi) && parseFloat(RegExp.$1); var webos = ua.match(/w(eb)?osbrowser/gi); var wppre8 = ua.match(/windows phone/gi) && ua.match(/iemobile\/([0-9])+/gi) && parseFloat(RegExp.$1) >= 9; var oldandroid = wkvers < 533 && ua.match(/android/gi); return webos || oldandroid || wppre8; }()); if (blacklist) { Modernizr.addTest('fontface', false); } else { testStyles('@font-face {font-family:"font";src:url("https://")}', function(node, rule) { var style = document.getElementById('smodernizr'); var sheet = style.sheet || style.styleSheet; var cssText = sheet ? (sheet.cssRules && sheet.cssRules[0] ? sheet.cssRules[0].cssText : sheet.cssText || '') : ''; var bool = /src/i.test(cssText) && cssText.indexOf(rule.split(' ')[0]) === 0; Modernizr.addTest('fontface', bool); }); } ; /*! { "name": "CSS Generated Content", "property": "generatedcontent", "tags": ["css"], "warnings": ["Android won't return correct height for anything below 7px #738"], "notes": [{ "name": "W3C CSS Selectors Level 3 spec", "href": "https://www.w3.org/TR/css3-selectors/#gen-content" },{ "name": "MDN article on :before", "href": "https://developer.mozilla.org/en-US/docs/Web/CSS/::before" },{ "name": "MDN article on :after", "href": "https://developer.mozilla.org/en-US/docs/Web/CSS/::before" }] } !*/ testStyles('#modernizr{font:0/0 a}#modernizr:after{content:":)";visibility:hidden;font:7px/1 a}', function(node) { Modernizr.addTest('generatedcontent', node.offsetHeight >= 7); }); 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} */ function contains(str, substr) { return !!~('' + str).indexOf(substr); } ; /** * cssToDOM takes a kebab-case string and converts it to camelCase * e.g. box-sizing -> boxSizing * * @access private * @function cssToDOM * @param {string} name - String name of kebab-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(/^-/, ''); } ; /** * 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.} 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 */ 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 overriden return fnBind(item, elem || obj); } // return the unbound function or obj or value return item; } } return false; } ; /** * 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; }); /** * domToCSS takes a camelCase string and converts it to kebab-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 kebab-case version of the supplied name */ function domToCSS(name) { return name.replace(/([A-Z])/g, function(str, m1) { return '-' + m1.toLowerCase(); }).replace(/^ms-/, '-ms-'); } ; /** * 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: http://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 getComputedStyle(node, null).position == 'absolute'; }); } return undefined; } ; // 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 kebab-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; } ; /** * testProp() investigates whether a given style property is recognized * Property names can be provided in either camelCase or kebab-case. * * @memberof Modernizr * @name Modernizr.testProp * @access public * @optionName Modernizr.testProp() * @optionProp testProp * @function testProp * @param {string} prop - Name of the CSS property to check * @param {string} [value] - Name of the CSS value to check * @param {boolean} [useValue] - Whether or not to check the value if @supports isn't supported * @returns {boolean} * @example * * Just like [testAllProps](#modernizr-testallprops), only it does not check any vendor prefixed * version of the string. * * Note that the property name must be provided in camelCase (e.g. boxSizing not box-sizing) * * ```js * Modernizr.testProp('pointerEvents') // true * ``` * * You can also provide a value as an optional second argument to check if a * specific value is supported * * ```js * Modernizr.testProp('pointerEvents', 'none') // true * Modernizr.testProp('pointerEvents', 'penguin') // false * ``` */ var testProp = ModernizrProto.testProp = function(prop, value, useValue) { return testProps([prop], undefined, value, useValue); }; /*! { "name": "CSS textshadow", "property": "textshadow", "caniuse": "css-textshadow", "tags": ["css"], "knownBugs": ["FF3.0 will false positive on this test"] } !*/ Modernizr.addTest('textshadow', testProp('textShadow', '1px 1px')); /** * 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 */ 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 kebab-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 * @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://goo.gl/OGw5Gm" }] } !*/ /* DOC Detects whether or not elements can be animated using CSS */ Modernizr.addTest('cssanimations', testAllProps('animationName', 'a', true)); /*! { "name": "Background Size", "property": "backgroundsize", "tags": ["css"], "knownBugs": ["This will false positive in Opera Mini - https://github.com/Modernizr/Modernizr/issues/396"], "notes": [{ "name": "Related Issue", "href": "https://github.com/Modernizr/Modernizr/issues/396" }] } !*/ Modernizr.addTest('backgroundsize', testAllProps('backgroundSize', '100%', true)); /*! { "name": "Border Image", "property": "borderimage", "caniuse": "border-image", "polyfills": ["css3pie"], "knownBugs": ["Android < 2.0 is true, but has a broken implementation"], "tags": ["css"] } !*/ Modernizr.addTest('borderimage', testAllProps('borderImage', 'url() 1', true)); /*! { "name": "Border Radius", "property": "borderradius", "caniuse": "border-radius", "polyfills": ["css3pie"], "tags": ["css"], "notes": [{ "name": "Comprehensive Compat Chart", "href": "https://muddledramblings.com/table-of-css3-border-radius-compliance" }] } !*/ Modernizr.addTest('borderradius', testAllProps('borderRadius', '0px', 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": "CSS Transforms", "property": "csstransforms", "caniuse": "transforms2d", "tags": ["css"] } !*/ Modernizr.addTest('csstransforms', function() { // Android < 3.0 is buggy, so we sniff and blacklist // http://git.io/hHzL7w return navigator.userAgent.indexOf('Android 2.') === -1 && testAllProps('transform', 'scale(1)', true); }); /*! { "name": "CSS Transforms 3D", "property": "csstransforms3d", "caniuse": "transforms3d", "tags": ["css"], "warnings": [ "Chrome may occassionally fail this test on some systems; more info: https://code.google.com/p/chromium/issues/detail?id=129004" ] } !*/ Modernizr.addTest('csstransforms3d', function() { var ret = !!testAllProps('perspective', '1px', true); var usePrefix = Modernizr._config.usePrefixes; // Webkit's 3D transforms are passed off to the browser's own graphics renderer. // It works fine in Safari on Leopard and Snow Leopard, but not in Chrome in // some conditions. As a result, Webkit typically recognizes the syntax but // will sometimes throw a false positive, thus we must do a more thorough check: if (ret && (!usePrefix || 'webkitPerspective' in docElement.style)) { var mq; var defaultStyle = '#modernizr{width:0;height:0}'; // Use CSS Conditional Rules if available if (Modernizr.supports) { mq = '@supports (perspective: 1px)'; } else { // Otherwise, Webkit allows this media query to succeed only if the feature is enabled. // `@media (transform-3d),(-webkit-transform-3d){ ... }` mq = '@media (transform-3d)'; if (usePrefix) { mq += ',(-webkit-transform-3d)'; } } mq += '{#modernizr{width:7px;height:18px;margin:0;padding:0;border:0}}'; testStyles(defaultStyle + mq, function(elem) { ret = elem.offsetWidth === 7 && elem.offsetHeight === 18; }); } return ret; }); /*! { "name": "CSS Transitions", "property": "csstransitions", "caniuse": "css-transitions", "tags": ["css"] } !*/ Modernizr.addTest('csstransitions', testAllProps('transition', 'all', true)); // Run each test testRunner(); // Remove the "no-js" class if it exists setClasses(classes); 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 window.Modernizr = Modernizr; ; })(window, document);