├── .eslintignore ├── .prettierignore ├── .github ├── FUNDING.yml └── workflows │ ├── main.yml │ └── publish.yml ├── .gitignore ├── .prettierrc.json ├── CHANGELOG.md ├── .eslintrc.json ├── index.html ├── script ├── buildModernizr.js └── featureList.json ├── LICENSE ├── README.md ├── src ├── style.scss ├── index.js └── modernizr.js ├── package.json └── webpack.config.js /.eslintignore: -------------------------------------------------------------------------------- 1 | /src/modernizr.js -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /src/modernizr.js -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: surunzi 2 | open_collective: eruda -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | eruda-features.js 3 | eruda-features.js.map 4 | package-lock.json -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "tabWidth": 2, 4 | "semi": false 5 | } 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v2.1.0 (5 Aug 2024) 2 | 3 | * feat: remove html5test link 4 | 5 | ## v2.0.0 (5 Jan 2020) 6 | 7 | * feat: theme support -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "amd": true, 6 | "commonjs": true 7 | }, 8 | "extends": "eslint:recommended", 9 | "rules": { 10 | "indent": ["error", 2], 11 | "quotes": ["error", "single"], 12 | "no-extra-semi": "off" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Eruda-features 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - 'master' 8 | paths: 9 | - 'src/**/*' 10 | 11 | jobs: 12 | ci: 13 | 14 | runs-on: ubuntu-latest 15 | 16 | strategy: 17 | matrix: 18 | node-version: [16.x] 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: Use Node.js ${{ matrix.node-version }} 23 | uses: actions/setup-node@v2 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | - run: npm i 27 | - run: npm run ci 28 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to NPM 2 | 3 | on: 4 | workflow_dispatch: 5 | release: 6 | types: [created] 7 | 8 | jobs: 9 | publish: 10 | 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v2 16 | - name: Setup Node 17 | uses: actions/setup-node@v2 18 | with: 19 | node-version: '18.x' 20 | registry-url: 'https://registry.npmjs.org' 21 | - name: Build eruda-features 22 | run: | 23 | npm i 24 | npm run build 25 | - name: Publish package on NPM 26 | run: npm publish 27 | env: 28 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} -------------------------------------------------------------------------------- /script/buildModernizr.js: -------------------------------------------------------------------------------- 1 | var modernizr = require('modernizr'), 2 | fs = require('fs'), 3 | path = require('path'); 4 | 5 | var featureList = require('./featureList.json'); 6 | 7 | modernizr.build(featureList, function (result) 8 | { 9 | result = result.replace(';(function(window, document, undefined){', '') 10 | .replace('window.Modernizr = Modernizr;', '') 11 | .replace('\'enableClasses\': true', '\'enableClasses\': false') 12 | .replace('testRunner();', 'Modernizr.testRunner = testRunner;') 13 | .replace('})(window, document);', ''); 14 | 15 | result += '\nmodule.exports = Modernizr;'; 16 | fs.writeFile(path.resolve(__dirname, '../src/modernizr.js'), result, 'utf8', function () {}); 17 | }); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 liriliri 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # eruda-features 2 | 3 | [![NPM version][npm-image]][npm-url] 4 | [![Build status][ci-image]][ci-url] 5 | [![License][license-image]][npm-url] 6 | 7 | [npm-image]: https://img.shields.io/npm/v/eruda-features.svg 8 | [npm-url]: https://npmjs.org/package/eruda-features 9 | [ci-image]: https://img.shields.io/github/actions/workflow/status/liriliri/eruda-features/main.yml?branch=master&style=flat-square 10 | [ci-url]: https://github.com/liriliri/eruda-features/actions/workflows/main.yml 11 | [license-image]: https://img.shields.io/npm/l/eruda-features.svg 12 | 13 | Eruda plugin for browser feature detections, thanks to [modernizr](https://github.com/Modernizr/Modernizr) project. 14 | 15 | Red means unsupported, otherwise ok. All buttons is linked directly to related materials in [Can I Use](http://caniuse.com/) website. 16 | 17 | ## Demo 18 | 19 | Browse it on your phone: 20 | [https://eruda.liriliri.io/?plugin=features](https://eruda.liriliri.io/?plugin=features) 21 | 22 | ## Install 23 | 24 | ```bash 25 | npm install eruda-features --save 26 | ``` 27 | 28 | ```javascript 29 | eruda.add(erudaFeatures); 30 | ``` 31 | 32 | Make sure Eruda is loaded before this plugin, otherwise won't work. -------------------------------------------------------------------------------- /src/style.scss: -------------------------------------------------------------------------------- 1 | @mixin overflow-auto($direction: 'both') { 2 | @if $direction == 'both' { 3 | overflow: auto; 4 | } @else { 5 | overflow-#{$direction}: auto; 6 | } 7 | -webkit-overflow-scrolling: touch; 8 | } 9 | 10 | @mixin clear-float { 11 | &:after { 12 | content: ''; 13 | display: block; 14 | clear: both; 15 | } 16 | } 17 | 18 | $font-size-s: 12px; 19 | 20 | .features { 21 | ul { 22 | height: 100%; 23 | @include overflow-auto(y); 24 | @include clear-float(); 25 | li { 26 | width: 33.3%; 27 | float: left; 28 | padding: 5px; 29 | .inner-wrapper { 30 | @include overflow-auto(x); 31 | font-size: $font-size-s; 32 | text-decoration: underline; 33 | display: block; 34 | padding: 10px; 35 | text-align: center; 36 | color: var(--console-error-foreground); 37 | background: var(--console-error-background); 38 | border: 1px solid var(--console-error-border); 39 | &.ok { 40 | background: var(--darker-background); 41 | border: 1px solid var(--border); 42 | color: var(--foreground); 43 | } 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eruda-features", 3 | "version": "2.1.0", 4 | "main": "eruda-features.js", 5 | "description": "Eruda plugin for browser feature detections", 6 | "browserslist": [ 7 | "since 2015", 8 | "not dead" 9 | ], 10 | "files": [ 11 | "eruda-features.js", 12 | "eruda-features.js.map" 13 | ], 14 | "scripts": { 15 | "dev": "webpack-dev-server --host 0.0.0.0 --mode development", 16 | "build": "webpack --mode production", 17 | "ci": "npm run lint && npm run build && npm run es5", 18 | "lint": "eslint src/**/*.js", 19 | "format": "lsla prettier \"src/*.{js,scss}\" \"*.json\" --write", 20 | "buildModernizr": "node script/buildModernizr", 21 | "es5": "es-check es5 eruda-features.js" 22 | }, 23 | "keywords": [ 24 | "eruda", 25 | "features", 26 | "plugin" 27 | ], 28 | "author": "redhoodsu", 29 | "license": "MIT", 30 | "bugs": { 31 | "url": "https://github.com/liriliri/eruda-features/issues" 32 | }, 33 | "homepage": "https://github.com/liriliri/eruda-features#readme", 34 | "devDependencies": { 35 | "@babel/core": "^7.21.3", 36 | "@babel/plugin-transform-runtime": "^7.21.0", 37 | "@babel/preset-env": "^7.20.2", 38 | "autoprefixer": "^10.4.14", 39 | "babel-loader": "^9.1.2", 40 | "css-loader": "^3.4.2", 41 | "eruda": "^3.2.0", 42 | "es-check": "^7.2.1", 43 | "eslint": "^8.57.0", 44 | "licia": "^1.41.1", 45 | "modernizr": "^3.13.0", 46 | "postcss": "^8.4.21", 47 | "postcss-class-prefix": "^0.3.0", 48 | "postcss-loader": "^7.0.2", 49 | "sass": "^1.77.8", 50 | "sass-loader": "^14.2.1", 51 | "webpack": "^5.93.0", 52 | "webpack-cli": "^5.1.4", 53 | "webpack-dev-server": "^4.12.0" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const map = require('licia/map') 2 | const modernizr = require('./modernizr') 3 | 4 | let featureList = require('../script/featureList.json') 5 | 6 | let featureNames = featureList['feature-detects'], 7 | specialNames = featureList['special-names'] 8 | 9 | module.exports = function (eruda) { 10 | let { evalCss } = eruda.util 11 | 12 | class Features extends eruda.Tool { 13 | constructor() { 14 | super() 15 | this.name = 'features' 16 | this._style = evalCss(require('./style.scss')) 17 | 18 | this._features = {} 19 | this._isInit = false 20 | } 21 | show() { 22 | super.show() 23 | 24 | if (!this._isInit) this._initFeatures() 25 | } 26 | hide() { 27 | super.hide() 28 | } 29 | destroy() { 30 | super.destroy() 31 | evalCss.remove(this._style) 32 | } 33 | _initFeatures() { 34 | this._isInit = true 35 | 36 | modernizr.testRunner() 37 | 38 | let i = 0, 39 | featureNum = featureNames.length 40 | 41 | featureNames.forEach((feature) => { 42 | if (specialNames[feature]) feature = specialNames[feature] 43 | feature = feature.replace(/\//g, '') 44 | 45 | modernizr.on(feature, (result) => { 46 | this._features[feature] = result 47 | i++ 48 | if (i === featureNum) this._render() 49 | }) 50 | }) 51 | } 52 | _render() { 53 | const features = map(this._features, (feature, key) => { 54 | const ok = feature ? 'eruda-ok' : '' 55 | 56 | return `
  • 57 | 58 | ${key} 59 | 60 |
  • ` 61 | }).join('') 62 | const html = `` 63 | this._$el.html(html) 64 | } 65 | } 66 | 67 | return new Features() 68 | } 69 | -------------------------------------------------------------------------------- /script/featureList.json: -------------------------------------------------------------------------------- 1 | { 2 | "feature-detects": [ 3 | "audio", 4 | "canvas", 5 | "cookies", 6 | "css/animations", 7 | "css/boxshadow", 8 | "css/boxsizing", 9 | "css/calc", 10 | "css/flexbox", 11 | "css/transforms", 12 | "css/transforms3d", 13 | "css/transitions", 14 | "es6/promises", 15 | "file/api", 16 | "file/filesystem", 17 | "forms/placeholder", 18 | "fullscreen-api", 19 | "geolocation", 20 | "hashchange", 21 | "history", 22 | "img/webp", 23 | "img/webp-alpha", 24 | "indexeddb", 25 | "json", 26 | "network/fetch", 27 | "network/xhr2", 28 | "notification", 29 | "performance", 30 | "pointerevents", 31 | "queryselector", 32 | "script/async", 33 | "script/defer", 34 | "serviceworker", 35 | "storage/localstorage", 36 | "storage/sessionstorage", 37 | "storage/websqldatabase", 38 | "style/scoped", 39 | "svg", 40 | "templatestrings", 41 | "touchevents", 42 | "typed-arrays", 43 | "url/bloburls", 44 | "url/data-uri", 45 | "video", 46 | "webgl", 47 | "websockets" 48 | ], 49 | "special-names": { 50 | "css/boxshadow": "boxshadow", 51 | "css/boxsizing": "boxsizing", 52 | "css/flexbox": "flexbox", 53 | "es6/promises": "promises", 54 | "file/api": "filereader", 55 | "file/filesystem": "filesystem", 56 | "forms/placeholder": "placeholder", 57 | "fullscreen-api": "fullscreen", 58 | "img/webp": "webp", 59 | "img/webp-alpha": "webpalpha", 60 | "network/fetch": "fetch", 61 | "network/xhr2": "xhr2", 62 | "storage/localstorage": "localstorage", 63 | "storage/sessionstorage": "sessionstorage", 64 | "storage/websqldatabase": "websqldatabase", 65 | "typed-arrays": "typedarrays", 66 | "url/bloburls": "bloburls", 67 | "url/data-uri": "datauri" 68 | } 69 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const autoprefixer = require('autoprefixer') 2 | const postcss = require('postcss') 3 | const webpack = require('webpack') 4 | const path = require('path') 5 | const pkg = require('./package.json') 6 | const classPrefix = require('postcss-class-prefix') 7 | const TerserPlugin = require('terser-webpack-plugin') 8 | 9 | const banner = pkg.name + ' v' + pkg.version + ' ' + pkg.homepage 10 | 11 | module.exports = (env, argv) => { 12 | const config = { 13 | devtool: 'source-map', 14 | entry: './src/index.js', 15 | devServer: { 16 | static: { 17 | directory: path.join(__dirname, './'), 18 | }, 19 | port: 8080, 20 | }, 21 | output: { 22 | path: __dirname, 23 | filename: 'eruda-features.js', 24 | publicPath: '/assets/', 25 | library: ['erudaFeatures'], 26 | libraryTarget: 'umd', 27 | }, 28 | module: { 29 | rules: [ 30 | { 31 | test: /\.js$/, 32 | exclude: /node_modules/, 33 | use: { 34 | loader: 'babel-loader', 35 | options: { 36 | sourceType: 'unambiguous', 37 | presets: ['@babel/preset-env'], 38 | plugins: ['@babel/plugin-transform-runtime'], 39 | }, 40 | }, 41 | }, 42 | { 43 | test: /\.scss$/, 44 | use: [ 45 | 'css-loader', 46 | { 47 | loader: 'postcss-loader', 48 | options: { 49 | postcssOptions: { 50 | plugins: [ 51 | postcss.plugin('postcss-namespace', function () { 52 | // Add '.dev-tools .tools ' to every selector. 53 | return function (root) { 54 | root.walkRules(function (rule) { 55 | if (!rule.selectors) return rule 56 | 57 | rule.selectors = rule.selectors.map(function ( 58 | selector 59 | ) { 60 | return '.dev-tools .tools ' + selector 61 | }) 62 | }) 63 | } 64 | }), 65 | classPrefix('eruda-'), 66 | autoprefixer, 67 | ], 68 | }, 69 | }, 70 | }, 71 | 'sass-loader', 72 | ], 73 | }, 74 | ], 75 | }, 76 | plugins: [new webpack.BannerPlugin(banner)], 77 | } 78 | 79 | if (argv.mode === 'production') { 80 | config.optimization = { 81 | minimize: true, 82 | minimizer: [ 83 | new TerserPlugin({ 84 | terserOptions: { 85 | format: { 86 | comments: false, 87 | }, 88 | }, 89 | extractComments: false, 90 | }), 91 | ], 92 | } 93 | } 94 | 95 | return config 96 | } 97 | -------------------------------------------------------------------------------- /src/modernizr.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * modernizr v3.13.0 3 | * 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 4 | * 5 | * Copyright (c) 6 | * Faruk Ates 7 | * Paul Irish 8 | * Alex Sexton 9 | * Ryan Seddon 10 | * Patrick Kettner 11 | * Stu Cox 12 | * Richard Herrera 13 | * Veeck 14 | 15 | * MIT License 16 | */ 17 | 18 | /* 19 | * Modernizr tests which native CSS3 and HTML5 features are available in the 20 | * current UA and makes the results available to you in two ways: as properties on 21 | * a global `Modernizr` object, and as classes on the `` element. This 22 | * information allows you to progressively enhance your pages with a granular level 23 | * of control over the experience. 24 | */ 25 | 26 | ;(function(scriptGlobalObject, window, document, undefined){ 27 | 28 | var tests = []; 29 | 30 | 31 | /** 32 | * ModernizrProto is the constructor for Modernizr 33 | * 34 | * @class 35 | * @access public 36 | */ 37 | var ModernizrProto = { 38 | _version: '3.13.0', 39 | 40 | // Any settings that don't work as separate modules 41 | // can go in here as configuration. 42 | _config: { 43 | 'classPrefix': '', 44 | 'enableClasses': false, 45 | 'enableJSClass': true, 46 | 'usePrefixes': true 47 | }, 48 | 49 | // Queue of tests 50 | _q: [], 51 | 52 | // Stub these for people who are listening 53 | on: function(test, cb) { 54 | // I don't really think people should do this, but we can 55 | // safe guard it a bit. 56 | // -- NOTE:: this gets WAY overridden in src/addTest for actual async tests. 57 | // This is in case people listen to synchronous tests. I would leave it out, 58 | // but the code to *disallow* sync tests in the real version of this 59 | // function is actually larger than this. 60 | var self = this; 61 | setTimeout(function() { 62 | cb(self[test]); 63 | }, 0); 64 | }, 65 | 66 | addTest: function(name, fn, options) { 67 | tests.push({name: name, fn: fn, options: options}); 68 | }, 69 | 70 | addAsyncTest: function(fn) { 71 | tests.push({name: null, fn: fn}); 72 | } 73 | }; 74 | 75 | 76 | 77 | // Fake some of Object.create so we can force non test results to be non "own" properties. 78 | var Modernizr = function() {}; 79 | Modernizr.prototype = ModernizrProto; 80 | 81 | // Leak modernizr globally when you `require` it rather than force it here. 82 | // Overwrite name so constructor name is nicer :D 83 | Modernizr = new Modernizr(); 84 | 85 | 86 | 87 | var classes = []; 88 | 89 | 90 | /** 91 | * is returns a boolean if the typeof an obj is exactly type. 92 | * 93 | * @access private 94 | * @function is 95 | * @param {*} obj - A thing we want to check the type of 96 | * @param {string} type - A string to compare the typeof against 97 | * @returns {boolean} true if the typeof the first parameter is exactly the specified type, false otherwise 98 | */ 99 | function is(obj, type) { 100 | return typeof obj === type; 101 | } 102 | 103 | ; 104 | 105 | /** 106 | * Run through all tests and detect their support in the current UA. 107 | * 108 | * @access private 109 | * @returns {void} 110 | */ 111 | function testRunner() { 112 | var featureNames; 113 | var feature; 114 | var aliasIdx; 115 | var result; 116 | var nameIdx; 117 | var featureName; 118 | var featureNameSplit; 119 | 120 | for (var featureIdx in tests) { 121 | if (tests.hasOwnProperty(featureIdx)) { 122 | featureNames = []; 123 | feature = tests[featureIdx]; 124 | // run the test, throw the return value into the Modernizr, 125 | // then based on that boolean, define an appropriate className 126 | // and push it into an array of classes we'll join later. 127 | // 128 | // If there is no name, it's an 'async' test that is run, 129 | // but not directly added to the object. That should 130 | // be done with a post-run addTest call. 131 | if (feature.name) { 132 | featureNames.push(feature.name.toLowerCase()); 133 | 134 | if (feature.options && feature.options.aliases && feature.options.aliases.length) { 135 | // Add all the aliases into the names list 136 | for (aliasIdx = 0; aliasIdx < feature.options.aliases.length; aliasIdx++) { 137 | featureNames.push(feature.options.aliases[aliasIdx].toLowerCase()); 138 | } 139 | } 140 | } 141 | 142 | // Run the test, or use the raw value if it's not a function 143 | result = is(feature.fn, 'function') ? feature.fn() : feature.fn; 144 | 145 | // Set each of the names on the Modernizr object 146 | for (nameIdx = 0; nameIdx < featureNames.length; nameIdx++) { 147 | featureName = featureNames[nameIdx]; 148 | // Support dot properties as sub tests. We don't do checking to make sure 149 | // that the implied parent tests have been added. You must call them in 150 | // order (either in the test, or make the parent test a dependency). 151 | // 152 | // Cap it to TWO to make the logic simple and because who needs that kind of subtesting 153 | // hashtag famous last words 154 | featureNameSplit = featureName.split('.'); 155 | 156 | if (featureNameSplit.length === 1) { 157 | Modernizr[featureNameSplit[0]] = result; 158 | } else { 159 | // cast to a Boolean, if not one already or if it doesnt exist yet (like inputtypes) 160 | if (!Modernizr[featureNameSplit[0]] || Modernizr[featureNameSplit[0]] && !(Modernizr[featureNameSplit[0]] instanceof Boolean)) { 161 | Modernizr[featureNameSplit[0]] = new Boolean(Modernizr[featureNameSplit[0]]); 162 | } 163 | 164 | Modernizr[featureNameSplit[0]][featureNameSplit[1]] = result; 165 | } 166 | 167 | classes.push((result ? '' : 'no-') + featureNameSplit.join('-')); 168 | } 169 | } 170 | } 171 | } 172 | ; 173 | 174 | /** 175 | * docElement is a convenience wrapper to grab the root element of the document 176 | * 177 | * @access private 178 | * @returns {HTMLElement|SVGElement} The root element of the document 179 | */ 180 | var docElement = document.documentElement; 181 | 182 | 183 | /** 184 | * A convenience helper to check if the document we are running in is an SVG document 185 | * 186 | * @access private 187 | * @returns {boolean} 188 | */ 189 | var isSVG = docElement.nodeName.toLowerCase() === 'svg'; 190 | 191 | 192 | 193 | /** 194 | * createElement is a convenience wrapper around document.createElement. Since we 195 | * use createElement all over the place, this allows for (slightly) smaller code 196 | * as well as abstracting away issues with creating elements in contexts other than 197 | * HTML documents (e.g. SVG documents). 198 | * 199 | * @access private 200 | * @function createElement 201 | * @returns {HTMLElement|SVGElement} An HTML or SVG element 202 | */ 203 | function createElement() { 204 | if (typeof document.createElement !== 'function') { 205 | // This is the case in IE7, where the type of createElement is "object". 206 | // For this reason, we cannot call apply() as Object is not a Function. 207 | return document.createElement(arguments[0]); 208 | } else if (isSVG) { 209 | return document.createElementNS.call(document, 'http://www.w3.org/2000/svg', arguments[0]); 210 | } else { 211 | return document.createElement.apply(document, arguments); 212 | } 213 | } 214 | 215 | ; 216 | /*! 217 | { 218 | "name": "HTML5 Audio Element", 219 | "property": "audio", 220 | "caniuse": "audio", 221 | "tags": ["html5", "audio", "media"], 222 | "notes": [{ 223 | "name": "MDN Docs", 224 | "href": "https://developer.mozilla.org/En/Media_formats_supported_by_the_audio_and_video_elements" 225 | }] 226 | } 227 | !*/ 228 | /* DOC 229 | Detects support of the audio element, as well as testing what types of content it supports. 230 | 231 | Subproperties are provided to describe support for `ogg`, `mp3`,`opus`, `wav` and `m4a` formats, e.g.: 232 | 233 | ```javascript 234 | Modernizr.audio // true 235 | Modernizr.audio.ogg // 'probably' 236 | ``` 237 | */ 238 | 239 | // Codec values from : github.com/NielsLeenheer/html5test/blob/9106a8/index.html#L845 240 | // thx to NielsLeenheer and zcorpan 241 | 242 | // Note: in some older browsers, "no" was a return value instead of empty string. 243 | // It was live in FF3.5.0 and 3.5.1, but fixed in 3.5.2 244 | // It was also live in Safari 4.0.0 - 4.0.4, but fixed in 4.0.5 245 | (function() { 246 | var elem = createElement('audio'); 247 | 248 | Modernizr.addTest('audio', function() { 249 | var bool = false; 250 | try { 251 | bool = !!elem.canPlayType; 252 | if (bool) { 253 | bool = new Boolean(bool); 254 | } 255 | } catch (e) {} 256 | 257 | return bool; 258 | }); 259 | 260 | // IE9 Running on Windows Server SKU can cause an exception to be thrown, bug #224 261 | try { 262 | if (!!elem.canPlayType) { 263 | Modernizr.addTest('audio.ogg', elem.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/, '')); 264 | Modernizr.addTest('audio.mp3', elem.canPlayType('audio/mpeg; codecs="mp3"').replace(/^no$/, '')); 265 | Modernizr.addTest('audio.opus', elem.canPlayType('audio/ogg; codecs="opus"') || 266 | elem.canPlayType('audio/webm; codecs="opus"').replace(/^no$/, '')); 267 | Modernizr.addTest('audio.wav', elem.canPlayType('audio/wav; codecs="1"').replace(/^no$/, '')); 268 | Modernizr.addTest('audio.m4a', (elem.canPlayType('audio/x-m4a;') || 269 | elem.canPlayType('audio/aac;')).replace(/^no$/, '')); 270 | } 271 | } catch (e) {} 272 | })(); 273 | 274 | /*! 275 | { 276 | "name": "Canvas", 277 | "property": "canvas", 278 | "caniuse": "canvas", 279 | "tags": ["canvas", "graphics"], 280 | "polyfills": ["flashcanvas", "excanvas", "slcanvas", "fxcanvas"] 281 | } 282 | !*/ 283 | /* DOC 284 | Detects support for the `` element for 2D drawing. 285 | */ 286 | 287 | // On the S60 and BB Storm, getContext exists, but always returns undefined 288 | // so we actually have to call getContext() to verify 289 | // github.com/Modernizr/Modernizr/issues/issue/97/ 290 | Modernizr.addTest('canvas', function() { 291 | var elem = createElement('canvas'); 292 | return !!(elem.getContext && elem.getContext('2d')); 293 | }); 294 | 295 | /*! 296 | { 297 | "name": "Cookies", 298 | "property": "cookies", 299 | "tags": ["storage"], 300 | "authors": ["tauren"] 301 | } 302 | !*/ 303 | /* DOC 304 | Detects whether cookie support is enabled. 305 | */ 306 | 307 | // https://github.com/Modernizr/Modernizr/issues/191 308 | 309 | Modernizr.addTest('cookies', function() { 310 | // navigator.cookieEnabled cannot detect custom or nuanced cookie blocking 311 | // configurations. For example, when blocking cookies via the Advanced 312 | // Privacy Settings in IE9, it always returns true. And there have been 313 | // issues in the past with site-specific exceptions. 314 | // Don't rely on it. 315 | 316 | // try..catch because some in situations `document.cookie` is exposed but throws a 317 | // SecurityError if you try to access it; e.g. documents created from data URIs 318 | // or in sandboxed iframes (depending on flags/context) 319 | try { 320 | // Create cookie 321 | document.cookie = 'cookietest=1'; 322 | var ret = document.cookie.indexOf('cookietest=') !== -1; 323 | // Delete cookie 324 | document.cookie = 'cookietest=1; expires=Thu, 01-Jan-1970 00:00:01 GMT'; 325 | return ret; 326 | } 327 | catch (e) { 328 | return false; 329 | } 330 | }); 331 | 332 | 333 | /** 334 | * If the browsers follow the spec, then they would expose vendor-specific styles as: 335 | * elem.style.WebkitBorderRadius 336 | * instead of something like the following (which is technically incorrect): 337 | * elem.style.webkitBorderRadius 338 | * 339 | * WebKit ghosts their properties in lowercase but Opera & Moz do not. 340 | * Microsoft uses a lowercase `ms` instead of the correct `Ms` in IE8+ 341 | * erik.eae.net/archives/2008/03/10/21.48.10/ 342 | * 343 | * More here: github.com/Modernizr/Modernizr/issues/issue/21 344 | * 345 | * @access private 346 | * @returns {string} The string representing the vendor-specific style properties 347 | */ 348 | var omPrefixes = 'Moz O ms Webkit'; 349 | 350 | 351 | var cssomPrefixes = (ModernizrProto._config.usePrefixes ? omPrefixes.split(' ') : []); 352 | ModernizrProto._cssomPrefixes = cssomPrefixes; 353 | 354 | 355 | /** 356 | * contains checks to see if a string contains another string 357 | * 358 | * @access private 359 | * @function contains 360 | * @param {string} str - The string we want to check for substrings 361 | * @param {string} substr - The substring we want to search the first string for 362 | * @returns {boolean} true if and only if the first string 'str' contains the second string 'substr' 363 | */ 364 | function contains(str, substr) { 365 | return !!~('' + str).indexOf(substr); 366 | } 367 | 368 | ; 369 | 370 | /** 371 | * Create our "modernizr" element that we do most feature tests on. 372 | * 373 | * @access private 374 | */ 375 | var modElem = { 376 | elem: createElement('modernizr') 377 | }; 378 | 379 | // Clean up this element 380 | Modernizr._q.push(function() { 381 | delete modElem.elem; 382 | }); 383 | 384 | 385 | 386 | var mStyle = { 387 | style: modElem.elem.style 388 | }; 389 | 390 | // kill ref for gc, must happen before mod.elem is removed, so we unshift on to 391 | // the front of the queue. 392 | Modernizr._q.unshift(function() { 393 | delete mStyle.style; 394 | }); 395 | 396 | 397 | 398 | /** 399 | * getBody returns the body of a document, or an element that can stand in for 400 | * the body if a real body does not exist 401 | * 402 | * @access private 403 | * @function getBody 404 | * @returns {HTMLElement|SVGElement} Returns the real body of a document, or an 405 | * artificially created element that stands in for the body 406 | */ 407 | function getBody() { 408 | // After page load injecting a fake body doesn't work so check if body exists 409 | var body = document.body; 410 | 411 | if (!body) { 412 | // Can't use the real body create a fake one. 413 | body = createElement(isSVG ? 'svg' : 'body'); 414 | body.fake = true; 415 | } 416 | 417 | return body; 418 | } 419 | 420 | ; 421 | 422 | /** 423 | * injectElementWithStyles injects an element with style element and some CSS rules 424 | * 425 | * @access private 426 | * @function injectElementWithStyles 427 | * @param {string} rule - String representing a css rule 428 | * @param {Function} callback - A function that is used to test the injected element 429 | * @param {number} [nodes] - An integer representing the number of additional nodes you want injected 430 | * @param {string[]} [testnames] - An array of strings that are used as ids for the additional nodes 431 | * @returns {boolean} the result of the specified callback test 432 | */ 433 | function injectElementWithStyles(rule, callback, nodes, testnames) { 434 | var mod = 'modernizr'; 435 | var style; 436 | var ret; 437 | var node; 438 | var docOverflow; 439 | var div = createElement('div'); 440 | var body = getBody(); 441 | 442 | if (parseInt(nodes, 10)) { 443 | // In order not to give false positives we create a node for each test 444 | // This also allows the method to scale for unspecified uses 445 | while (nodes--) { 446 | node = createElement('div'); 447 | node.id = testnames ? testnames[nodes] : mod + (nodes + 1); 448 | div.appendChild(node); 449 | } 450 | } 451 | 452 | style = createElement('style'); 453 | style.type = 'text/css'; 454 | style.id = 's' + mod; 455 | 456 | // 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. 457 | // Opera will act all quirky when injecting elements in documentElement when page is served as xml, needs fakebody too. #270 458 | (!body.fake ? div : body).appendChild(style); 459 | body.appendChild(div); 460 | 461 | if (style.styleSheet) { 462 | style.styleSheet.cssText = rule; 463 | } else { 464 | style.appendChild(document.createTextNode(rule)); 465 | } 466 | div.id = mod; 467 | 468 | if (body.fake) { 469 | //avoid crashing IE8, if background image is used 470 | body.style.background = ''; 471 | //Safari 5.13/5.1.4 OSX stops loading if ::-webkit-scrollbar is used and scrollbars are visible 472 | body.style.overflow = 'hidden'; 473 | docOverflow = docElement.style.overflow; 474 | docElement.style.overflow = 'hidden'; 475 | docElement.appendChild(body); 476 | } 477 | 478 | ret = callback(div, rule); 479 | // If this is done after page load we don't want to remove the body so check if body exists 480 | if (body.fake && body.parentNode) { 481 | body.parentNode.removeChild(body); 482 | docElement.style.overflow = docOverflow; 483 | // Trigger layout so kinetic scrolling isn't disabled in iOS6+ 484 | // eslint-disable-next-line 485 | docElement.offsetHeight; 486 | } else { 487 | div.parentNode.removeChild(div); 488 | } 489 | 490 | return !!ret; 491 | } 492 | 493 | ; 494 | 495 | /** 496 | * domToCSS takes a camelCase string and converts it to hyphen-case 497 | * e.g. boxSizing -> box-sizing 498 | * 499 | * @access private 500 | * @function domToCSS 501 | * @param {string} name - String name of camelCase prop we want to convert 502 | * @returns {string} The hyphen-case version of the supplied name 503 | */ 504 | function domToCSS(name) { 505 | return name.replace(/([A-Z])/g, function(str, m1) { 506 | return '-' + m1.toLowerCase(); 507 | }).replace(/^ms-/, '-ms-'); 508 | } 509 | 510 | ; 511 | 512 | 513 | /** 514 | * wrapper around getComputedStyle, to fix issues with Firefox returning null when 515 | * called inside of a hidden iframe 516 | * 517 | * @access private 518 | * @function computedStyle 519 | * @param {HTMLElement|SVGElement} elem - The element we want to find the computed styles of 520 | * @param {string|null} [pseudo] - An optional pseudo element selector (e.g. :before), of null if none 521 | * @param {string} prop - A CSS property 522 | * @returns {CSSStyleDeclaration} the value of the specified CSS property 523 | */ 524 | function computedStyle(elem, pseudo, prop) { 525 | var result; 526 | 527 | if ('getComputedStyle' in window) { 528 | result = getComputedStyle.call(window, elem, pseudo); 529 | var console = window.console; 530 | 531 | if (result !== null) { 532 | if (prop) { 533 | result = result.getPropertyValue(prop); 534 | } 535 | } else { 536 | if (console) { 537 | var method = console.error ? 'error' : 'log'; 538 | console[method].call(console, 'getComputedStyle returning null, its possible modernizr test results are inaccurate'); 539 | } 540 | } 541 | } else { 542 | result = !pseudo && elem.currentStyle && elem.currentStyle[prop]; 543 | } 544 | 545 | return result; 546 | } 547 | 548 | ; 549 | 550 | /** 551 | * nativeTestProps allows for us to use native feature detection functionality if available. 552 | * some prefixed form, or false, in the case of an unsupported rule 553 | * 554 | * @access private 555 | * @function nativeTestProps 556 | * @param {Array} props - An array of property names 557 | * @param {string} value - A string representing the value we want to check via @supports 558 | * @returns {boolean|undefined} A boolean when @supports exists, undefined otherwise 559 | */ 560 | // Accepts a list of property names and a single value 561 | // Returns `undefined` if native detection not available 562 | function nativeTestProps(props, value) { 563 | var i = props.length; 564 | // Start with the JS API: https://www.w3.org/TR/css3-conditional/#the-css-interface 565 | if ('CSS' in window && 'supports' in window.CSS) { 566 | // Try every prefixed variant of the property 567 | while (i--) { 568 | if (window.CSS.supports(domToCSS(props[i]), value)) { 569 | return true; 570 | } 571 | } 572 | return false; 573 | } 574 | // Otherwise fall back to at-rule (for Opera 12.x) 575 | else if ('CSSSupportsRule' in window) { 576 | // Build a condition string for every prefixed variant 577 | var conditionText = []; 578 | while (i--) { 579 | conditionText.push('(' + domToCSS(props[i]) + ':' + value + ')'); 580 | } 581 | conditionText = conditionText.join(' or '); 582 | return injectElementWithStyles('@supports (' + conditionText + ') { #modernizr { position: absolute; } }', function(node) { 583 | return computedStyle(node, null, 'position') === 'absolute'; 584 | }); 585 | } 586 | return undefined; 587 | } 588 | ; 589 | 590 | /** 591 | * cssToDOM takes a hyphen-case string and converts it to camelCase 592 | * e.g. box-sizing -> boxSizing 593 | * 594 | * @access private 595 | * @function cssToDOM 596 | * @param {string} name - String name of hyphen-case prop we want to convert 597 | * @returns {string} The camelCase version of the supplied name 598 | */ 599 | function cssToDOM(name) { 600 | return name.replace(/([a-z])-([a-z])/g, function(str, m1, m2) { 601 | return m1 + m2.toUpperCase(); 602 | }).replace(/^-/, ''); 603 | } 604 | 605 | ; 606 | 607 | // testProps is a generic CSS / DOM property test. 608 | 609 | // In testing support for a given CSS property, it's legit to test: 610 | // `elem.style[styleName] !== undefined` 611 | // If the property is supported it will return an empty string, 612 | // if unsupported it will return undefined. 613 | 614 | // We'll take advantage of this quick test and skip setting a style 615 | // on our modernizr element, but instead just testing undefined vs 616 | // empty string. 617 | 618 | // Property names can be provided in either camelCase or hyphen-case. 619 | 620 | function testProps(props, prefixed, value, skipValueTest) { 621 | skipValueTest = is(skipValueTest, 'undefined') ? false : skipValueTest; 622 | 623 | // Try native detect first 624 | if (!is(value, 'undefined')) { 625 | var result = nativeTestProps(props, value); 626 | if (!is(result, 'undefined')) { 627 | return result; 628 | } 629 | } 630 | 631 | // Otherwise do it properly 632 | var afterInit, i, propsLength, prop, before; 633 | 634 | // If we don't have a style element, that means we're running async or after 635 | // the core tests, so we'll need to create our own elements to use. 636 | 637 | // Inside of an SVG element, in certain browsers, the `style` element is only 638 | // defined for valid tags. Therefore, if `modernizr` does not have one, we 639 | // fall back to a less used element and hope for the best. 640 | // For strict XHTML browsers the hardly used samp element is used. 641 | var elems = ['modernizr', 'tspan', 'samp']; 642 | while (!mStyle.style && elems.length) { 643 | afterInit = true; 644 | mStyle.modElem = createElement(elems.shift()); 645 | mStyle.style = mStyle.modElem.style; 646 | } 647 | 648 | // Delete the objects if we created them. 649 | function cleanElems() { 650 | if (afterInit) { 651 | delete mStyle.style; 652 | delete mStyle.modElem; 653 | } 654 | } 655 | 656 | propsLength = props.length; 657 | for (i = 0; i < propsLength; i++) { 658 | prop = props[i]; 659 | before = mStyle.style[prop]; 660 | 661 | if (contains(prop, '-')) { 662 | prop = cssToDOM(prop); 663 | } 664 | 665 | if (mStyle.style[prop] !== undefined) { 666 | 667 | // If value to test has been passed in, do a set-and-check test. 668 | // 0 (integer) is a valid property value, so check that `value` isn't 669 | // undefined, rather than just checking it's truthy. 670 | if (!skipValueTest && !is(value, 'undefined')) { 671 | 672 | // Needs a try catch block because of old IE. This is slow, but will 673 | // be avoided in most cases because `skipValueTest` will be used. 674 | try { 675 | mStyle.style[prop] = value; 676 | } catch (e) {} 677 | 678 | // If the property value has changed, we assume the value used is 679 | // supported. If `value` is empty string, it'll fail here (because 680 | // it hasn't changed), which matches how browsers have implemented 681 | // CSS.supports() 682 | if (mStyle.style[prop] !== before) { 683 | cleanElems(); 684 | return prefixed === 'pfx' ? prop : true; 685 | } 686 | } 687 | // Otherwise just return true, or the property name if this is a 688 | // `prefixed()` call 689 | else { 690 | cleanElems(); 691 | return prefixed === 'pfx' ? prop : true; 692 | } 693 | } 694 | } 695 | cleanElems(); 696 | return false; 697 | } 698 | 699 | ; 700 | 701 | /** 702 | * List of JavaScript DOM values used for tests 703 | * 704 | * @memberOf Modernizr 705 | * @name Modernizr._domPrefixes 706 | * @optionName Modernizr._domPrefixes 707 | * @optionProp domPrefixes 708 | * @access public 709 | * @example 710 | * 711 | * Modernizr._domPrefixes is exactly the same as [_prefixes](#modernizr-_prefixes), but rather 712 | * than hyphen-case properties, all properties are their Capitalized variant 713 | * 714 | * ```js 715 | * Modernizr._domPrefixes === [ "Moz", "O", "ms", "Webkit" ]; 716 | * ``` 717 | */ 718 | var domPrefixes = (ModernizrProto._config.usePrefixes ? omPrefixes.toLowerCase().split(' ') : []); 719 | ModernizrProto._domPrefixes = domPrefixes; 720 | 721 | 722 | /** 723 | * fnBind is a super small [bind](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) polyfill. 724 | * 725 | * @access private 726 | * @function fnBind 727 | * @param {Function} fn - a function you want to change `this` reference to 728 | * @param {object} that - the `this` you want to call the function with 729 | * @returns {Function} The wrapped version of the supplied function 730 | */ 731 | function fnBind(fn, that) { 732 | return function() { 733 | return fn.apply(that, arguments); 734 | }; 735 | } 736 | 737 | ; 738 | 739 | /** 740 | * testDOMProps is a generic DOM property test; if a browser supports 741 | * a certain property, it won't return undefined for it. 742 | * 743 | * @access private 744 | * @function testDOMProps 745 | * @param {Array} props - An array of properties to test for 746 | * @param {object} obj - An object or Element you want to use to test the parameters again 747 | * @param {boolean|object} elem - An Element to bind the property lookup again. Use `false` to prevent the check 748 | * @returns {boolean|*} returns `false` if the prop is unsupported, otherwise the value that is supported 749 | */ 750 | function testDOMProps(props, obj, elem) { 751 | var item; 752 | 753 | for (var i in props) { 754 | if (props[i] in obj) { 755 | 756 | // return the property name as a string 757 | if (elem === false) { 758 | return props[i]; 759 | } 760 | 761 | item = obj[props[i]]; 762 | 763 | // let's bind a function 764 | if (is(item, 'function')) { 765 | // bind to obj unless overridden 766 | return fnBind(item, elem || obj); 767 | } 768 | 769 | // return the unbound function or obj or value 770 | return item; 771 | } 772 | } 773 | return false; 774 | } 775 | 776 | ; 777 | 778 | /** 779 | * testPropsAll tests a list of DOM properties we want to check against. 780 | * We specify literally ALL possible (known and/or likely) properties on 781 | * the element including the non-vendor prefixed one, for forward- 782 | * compatibility. 783 | * 784 | * @access private 785 | * @function testPropsAll 786 | * @param {string} prop - A string of the property to test for 787 | * @param {string|object} [prefixed] - An object to check the prefixed properties on. Use a string to skip 788 | * @param {HTMLElement|SVGElement} [elem] - An element used to test the property and value against 789 | * @param {string} [value] - A string of a css value 790 | * @param {boolean} [skipValueTest] - An boolean representing if you want to test if value sticks when set 791 | * @returns {string|boolean} returns the string version of the property, or `false` if it is unsupported 792 | */ 793 | function testPropsAll(prop, prefixed, elem, value, skipValueTest) { 794 | 795 | var ucProp = prop.charAt(0).toUpperCase() + prop.slice(1), 796 | props = (prop + ' ' + cssomPrefixes.join(ucProp + ' ') + ucProp).split(' '); 797 | 798 | // did they call .prefixed('boxSizing') or are we just testing a prop? 799 | if (is(prefixed, 'string') || is(prefixed, 'undefined')) { 800 | return testProps(props, prefixed, value, skipValueTest); 801 | 802 | // otherwise, they called .prefixed('requestAnimationFrame', window[, elem]) 803 | } else { 804 | props = (prop + ' ' + (domPrefixes).join(ucProp + ' ') + ucProp).split(' '); 805 | return testDOMProps(props, prefixed, elem); 806 | } 807 | } 808 | 809 | // Modernizr.testAllProps() investigates whether a given style property, 810 | // or any of its vendor-prefixed variants, is recognized 811 | // 812 | // Note that the property names must be provided in the camelCase variant. 813 | // Modernizr.testAllProps('boxSizing') 814 | ModernizrProto.testAllProps = testPropsAll; 815 | 816 | 817 | 818 | /** 819 | * testAllProps determines whether a given CSS property is supported in the browser 820 | * 821 | * @memberOf Modernizr 822 | * @name Modernizr.testAllProps 823 | * @optionName Modernizr.testAllProps() 824 | * @optionProp testAllProps 825 | * @access public 826 | * @function testAllProps 827 | * @param {string} prop - String naming the property to test (either camelCase or hyphen-case) 828 | * @param {string} [value] - String of the value to test 829 | * @param {boolean} [skipValueTest=false] - Whether to skip testing that the value is supported when using non-native detection 830 | * @returns {string|boolean} returns the string version of the property, or `false` if it is unsupported 831 | * @example 832 | * 833 | * testAllProps determines whether a given CSS property, in some prefixed form, 834 | * is supported by the browser. 835 | * 836 | * ```js 837 | * testAllProps('boxSizing') // true 838 | * ``` 839 | * 840 | * It can optionally be given a CSS value in string form to test if a property 841 | * value is valid 842 | * 843 | * ```js 844 | * testAllProps('display', 'block') // true 845 | * testAllProps('display', 'penguin') // false 846 | * ``` 847 | * 848 | * A boolean can be passed as a third parameter to skip the value check when 849 | * native detection (@supports) isn't available. 850 | * 851 | * ```js 852 | * testAllProps('shapeOutside', 'content-box', true); 853 | * ``` 854 | */ 855 | function testAllProps(prop, value, skipValueTest) { 856 | return testPropsAll(prop, undefined, undefined, value, skipValueTest); 857 | } 858 | 859 | ModernizrProto.testAllProps = testAllProps; 860 | 861 | 862 | /*! 863 | { 864 | "name": "CSS Animations", 865 | "property": "cssanimations", 866 | "caniuse": "css-animation", 867 | "polyfills": ["transformie", "csssandpaper"], 868 | "tags": ["css"], 869 | "warnings": ["Android < 4 will pass this test, but can only animate a single property at a time"], 870 | "notes": [{ 871 | "name": "Article: 'Dispelling the Android CSS animation myths'", 872 | "href": "https://web.archive.org/web/20180602074607/https://daneden.me/2011/12/14/putting-up-with-androids-bullshit/" 873 | }] 874 | } 875 | !*/ 876 | /* DOC 877 | Detects whether or not elements can be animated using CSS 878 | */ 879 | 880 | Modernizr.addTest('cssanimations', testAllProps('animationName', 'a', true)); 881 | 882 | /*! 883 | { 884 | "name": "Box Shadow", 885 | "property": "boxshadow", 886 | "caniuse": "css-boxshadow", 887 | "tags": ["css"], 888 | "knownBugs": [ 889 | "WebOS false positives on this test.", 890 | "The Kindle Silk browser false positives" 891 | ] 892 | } 893 | !*/ 894 | 895 | Modernizr.addTest('boxshadow', testAllProps('boxShadow', '1px 1px', true)); 896 | 897 | /*! 898 | { 899 | "name": "Box Sizing", 900 | "property": "boxsizing", 901 | "caniuse": "css3-boxsizing", 902 | "polyfills": ["borderboxmodel", "boxsizingpolyfill", "borderbox"], 903 | "tags": ["css"], 904 | "builderAliases": ["css_boxsizing"], 905 | "notes": [{ 906 | "name": "MDN Docs", 907 | "href": "https://developer.mozilla.org/en-US/docs/Web/CSS/box-sizing" 908 | }, { 909 | "name": "Related Github Issue", 910 | "href": "https://github.com/Modernizr/Modernizr/issues/248" 911 | }] 912 | } 913 | !*/ 914 | 915 | Modernizr.addTest('boxsizing', testAllProps('boxSizing', 'border-box', true) && (document.documentMode === undefined || document.documentMode > 7)); 916 | 917 | 918 | /** 919 | * List of property values to set for css tests. See ticket #21 920 | * https://github.com/modernizr/modernizr/issues/21 921 | * 922 | * @memberOf Modernizr 923 | * @name Modernizr._prefixes 924 | * @optionName Modernizr._prefixes 925 | * @optionProp prefixes 926 | * @access public 927 | * @example 928 | * 929 | * Modernizr._prefixes is the internal list of prefixes that we test against 930 | * inside of things like [prefixed](#modernizr-prefixed) and [prefixedCSS](#-code-modernizr-prefixedcss). It is simply 931 | * an array of hyphen-case vendor prefixes you can use within your code. 932 | * 933 | * Some common use cases include 934 | * 935 | * Generating all possible prefixed version of a CSS property 936 | * ```js 937 | * var rule = Modernizr._prefixes.join('transform: rotate(20deg); '); 938 | * 939 | * rule === 'transform: rotate(20deg); webkit-transform: rotate(20deg); moz-transform: rotate(20deg); o-transform: rotate(20deg); ms-transform: rotate(20deg);' 940 | * ``` 941 | * 942 | * Generating all possible prefixed version of a CSS value 943 | * ```js 944 | * rule = 'display:' + Modernizr._prefixes.join('flex; display:') + 'flex'; 945 | * 946 | * rule === 'display:flex; display:-webkit-flex; display:-moz-flex; display:-o-flex; display:-ms-flex; display:flex' 947 | * ``` 948 | */ 949 | // we use ['',''] rather than an empty array in order to allow a pattern of .`join()`ing prefixes to test 950 | // values in feature detects to continue to work 951 | var prefixes = (ModernizrProto._config.usePrefixes ? ' -webkit- -moz- -o- -ms- '.split(' ') : ['','']); 952 | 953 | // expose these for the plugin API. Look in the source for how to join() them against your input 954 | ModernizrProto._prefixes = prefixes; 955 | 956 | 957 | /*! 958 | { 959 | "name": "CSS Calc", 960 | "property": "csscalc", 961 | "caniuse": "calc", 962 | "tags": ["css"], 963 | "builderAliases": ["css_calc"], 964 | "authors": ["@calvein"] 965 | } 966 | !*/ 967 | /* DOC 968 | Method of allowing calculated values for length units. For example: 969 | 970 | ```css 971 | //lem { 972 | width: calc(100% - 3em); 973 | } 974 | ``` 975 | */ 976 | 977 | Modernizr.addTest('csscalc', function() { 978 | var prop = 'width:'; 979 | var value = 'calc(10px);'; 980 | var el = createElement('a'); 981 | 982 | el.style.cssText = prop + prefixes.join(value + prop); 983 | 984 | return !!el.style.length; 985 | }); 986 | 987 | /*! 988 | { 989 | "name": "Flexbox", 990 | "property": "flexbox", 991 | "caniuse": "flexbox", 992 | "tags": ["css"], 993 | "notes": [{ 994 | "name": "The _new_ flexbox", 995 | "href": "https://www.w3.org/TR/css-flexbox-1/" 996 | }], 997 | "warnings": [ 998 | "A `true` result for this detect does not imply that the `flex-wrap` property is supported; see the `flexwrap` detect." 999 | ] 1000 | } 1001 | !*/ 1002 | /* DOC 1003 | Detects support for the Flexible Box Layout model, a.k.a. Flexbox, which allows easy manipulation of layout order and sizing within a container. 1004 | */ 1005 | 1006 | Modernizr.addTest('flexbox', testAllProps('flexBasis', '1px', true)); 1007 | 1008 | /*! 1009 | { 1010 | "name": "CSS Transforms", 1011 | "property": "csstransforms", 1012 | "caniuse": "transforms2d", 1013 | "tags": ["css"] 1014 | } 1015 | !*/ 1016 | 1017 | Modernizr.addTest('csstransforms', function() { 1018 | // Android < 3.0 is buggy, so we sniff and reject it 1019 | // https://github.com/Modernizr/Modernizr/issues/903 1020 | return navigator.userAgent.indexOf('Android 2.') === -1 && 1021 | testAllProps('transform', 'scale(1)', true); 1022 | }); 1023 | 1024 | /*! 1025 | { 1026 | "name": "CSS Supports", 1027 | "property": "supports", 1028 | "caniuse": "css-featurequeries", 1029 | "tags": ["css"], 1030 | "builderAliases": ["css_supports"], 1031 | "notes": [{ 1032 | "name": "W3C Spec (The @supports rule)", 1033 | "href": "https://dev.w3.org/csswg/css3-conditional/#at-supports" 1034 | }, { 1035 | "name": "Related Github Issue", 1036 | "href": "https://github.com/Modernizr/Modernizr/issues/648" 1037 | }, { 1038 | "name": "W3C Spec (The CSSSupportsRule interface)", 1039 | "href": "https://dev.w3.org/csswg/css3-conditional/#the-csssupportsrule-interface" 1040 | }] 1041 | } 1042 | !*/ 1043 | 1044 | var newSyntax = 'CSS' in window && 'supports' in window.CSS; 1045 | var oldSyntax = 'supportsCSS' in window; 1046 | Modernizr.addTest('supports', newSyntax || oldSyntax); 1047 | 1048 | /*! 1049 | { 1050 | "name": "CSS Transforms 3D", 1051 | "property": "csstransforms3d", 1052 | "caniuse": "transforms3d", 1053 | "tags": ["css"], 1054 | "warnings": [ 1055 | "Chrome may occasionally fail this test on some systems; more info: https://bugs.chromium.org/p/chromium/issues/detail?id=129004" 1056 | ] 1057 | } 1058 | !*/ 1059 | 1060 | Modernizr.addTest('csstransforms3d', function() { 1061 | return !!testAllProps('perspective', '1px', true); 1062 | }); 1063 | 1064 | /*! 1065 | { 1066 | "name": "CSS Transitions", 1067 | "property": "csstransitions", 1068 | "caniuse": "css-transitions", 1069 | "tags": ["css"] 1070 | } 1071 | !*/ 1072 | 1073 | Modernizr.addTest('csstransitions', testAllProps('transition', 'all', true)); 1074 | 1075 | /*! 1076 | { 1077 | "name": "ES6 Promises", 1078 | "property": "promises", 1079 | "caniuse": "promises", 1080 | "polyfills": ["es6promises"], 1081 | "authors": ["Krister Kari", "Jake Archibald"], 1082 | "tags": ["es6"], 1083 | "notes": [{ 1084 | "name": "The ES6 promises spec", 1085 | "href": "https://github.com/domenic/promises-unwrapping" 1086 | }, { 1087 | "name": "Chromium dashboard - ES6 Promises", 1088 | "href": "https://www.chromestatus.com/features/5681726336532480" 1089 | }, { 1090 | "name": "JavaScript Promises: an Introduction", 1091 | "href": "https://developers.google.com/web/fundamentals/primers/promises/" 1092 | }] 1093 | } 1094 | !*/ 1095 | /* DOC 1096 | Check if browser implements ECMAScript 6 Promises per specification. 1097 | */ 1098 | 1099 | Modernizr.addTest('promises', function() { 1100 | return 'Promise' in window && 1101 | // Some of these methods are missing from 1102 | // Firefox/Chrome experimental implementations 1103 | 'resolve' in window.Promise && 1104 | 'reject' in window.Promise && 1105 | 'all' in window.Promise && 1106 | 'race' in window.Promise && 1107 | // Older version of the spec had a resolver object 1108 | // as the arg rather than a function 1109 | (function() { 1110 | var resolve; 1111 | new window.Promise(function(r) { resolve = r; }); 1112 | return typeof resolve === 'function'; 1113 | }()); 1114 | }); 1115 | 1116 | /*! 1117 | { 1118 | "name": "File API", 1119 | "property": "filereader", 1120 | "caniuse": "fileapi", 1121 | "notes": [{ 1122 | "name": "W3C Working Draft Spec", 1123 | "href": "https://www.w3.org/TR/FileAPI/" 1124 | }], 1125 | "tags": ["file"], 1126 | "builderAliases": ["file_api"], 1127 | "knownBugs": ["Will fail in Safari 5 due to its lack of support for the standards defined FileReader object"] 1128 | } 1129 | !*/ 1130 | /* DOC 1131 | `filereader` tests for the File API specification 1132 | 1133 | Tests for objects specific to the File API W3C specification without 1134 | being redundant (don't bother testing for Blob since it is assumed 1135 | to be the File object's prototype.) 1136 | */ 1137 | 1138 | Modernizr.addTest('filereader', !!(window.File && window.FileList && window.FileReader)); 1139 | 1140 | 1141 | /** 1142 | * atRule returns a given CSS property at-rule (eg @keyframes), possibly in 1143 | * some prefixed form, or false, in the case of an unsupported rule 1144 | * 1145 | * @memberOf Modernizr 1146 | * @name Modernizr.atRule 1147 | * @optionName Modernizr.atRule() 1148 | * @optionProp atRule 1149 | * @access public 1150 | * @function atRule 1151 | * @param {string} prop - String name of the @-rule to test for 1152 | * @returns {string|boolean} The string representing the (possibly prefixed) 1153 | * valid version of the @-rule, or `false` when it is unsupported. 1154 | * @example 1155 | * ```js 1156 | * var keyframes = Modernizr.atRule('@keyframes'); 1157 | * 1158 | * if (keyframes) { 1159 | * // keyframes are supported 1160 | * // could be `@-webkit-keyframes` or `@keyframes` 1161 | * } else { 1162 | * // keyframes === `false` 1163 | * } 1164 | * ``` 1165 | */ 1166 | var atRule = function(prop) { 1167 | var length = prefixes.length; 1168 | var cssrule = window.CSSRule; 1169 | var rule; 1170 | 1171 | if (typeof cssrule === 'undefined') { 1172 | return undefined; 1173 | } 1174 | 1175 | if (!prop) { 1176 | return false; 1177 | } 1178 | 1179 | // remove literal @ from beginning of provided property 1180 | prop = prop.replace(/^@/, ''); 1181 | 1182 | // CSSRules use underscores instead of dashes 1183 | rule = prop.replace(/-/g, '_').toUpperCase() + '_RULE'; 1184 | 1185 | if (rule in cssrule) { 1186 | return '@' + prop; 1187 | } 1188 | 1189 | for (var i = 0; i < length; i++) { 1190 | // prefixes gives us something like -o-, and we want O_ 1191 | var prefix = prefixes[i]; 1192 | var thisRule = prefix.toUpperCase() + '_' + rule; 1193 | 1194 | if (thisRule in cssrule) { 1195 | return '@-' + prefix.toLowerCase() + '-' + prop; 1196 | } 1197 | } 1198 | 1199 | return false; 1200 | }; 1201 | 1202 | ModernizrProto.atRule = atRule; 1203 | 1204 | 1205 | 1206 | /** 1207 | * prefixed returns the prefixed or nonprefixed property name variant of your input 1208 | * 1209 | * @memberOf Modernizr 1210 | * @name Modernizr.prefixed 1211 | * @optionName Modernizr.prefixed() 1212 | * @optionProp prefixed 1213 | * @access public 1214 | * @function prefixed 1215 | * @param {string} prop - String name of the property to test for 1216 | * @param {object} [obj] - An object to test for the prefixed properties on 1217 | * @param {HTMLElement} [elem] - An element used to test specific properties against 1218 | * @returns {string|boolean} The string representing the (possibly prefixed) valid 1219 | * version of the property, or `false` when it is unsupported. 1220 | * @example 1221 | * 1222 | * Modernizr.prefixed takes a string css value in the DOM style camelCase (as 1223 | * opposed to the css style hyphen-case) form and returns the (possibly prefixed) 1224 | * version of that property that the browser actually supports. 1225 | * 1226 | * For example, in older Firefox... 1227 | * ```js 1228 | * prefixed('boxSizing') 1229 | * ``` 1230 | * returns 'MozBoxSizing' 1231 | * 1232 | * In newer Firefox, as well as any other browser that support the unprefixed 1233 | * version would simply return `boxSizing`. Any browser that does not support 1234 | * the property at all, it will return `false`. 1235 | * 1236 | * By default, prefixed is checked against a DOM element. If you want to check 1237 | * for a property on another object, just pass it as a second argument 1238 | * 1239 | * ```js 1240 | * var rAF = prefixed('requestAnimationFrame', window); 1241 | * 1242 | * raf(function() { 1243 | * renderFunction(); 1244 | * }) 1245 | * ``` 1246 | * 1247 | * Note that this will return _the actual function_ - not the name of the function. 1248 | * If you need the actual name of the property, pass in `false` as a third argument 1249 | * 1250 | * ```js 1251 | * var rAFProp = prefixed('requestAnimationFrame', window, false); 1252 | * 1253 | * rafProp === 'WebkitRequestAnimationFrame' // in older webkit 1254 | * ``` 1255 | * 1256 | * One common use case for prefixed is if you're trying to determine which transition 1257 | * end event to bind to, you might do something like... 1258 | * ```js 1259 | * var transEndEventNames = { 1260 | * 'WebkitTransition' : 'webkitTransitionEnd', * Saf 6, Android Browser 1261 | * 'MozTransition' : 'transitionend', * only for FF < 15 1262 | * 'transition' : 'transitionend' * IE10, Opera, Chrome, FF 15+, Saf 7+ 1263 | * }; 1264 | * 1265 | * var transEndEventName = transEndEventNames[ Modernizr.prefixed('transition') ]; 1266 | * ``` 1267 | * 1268 | * If you want a similar lookup, but in hyphen-case, you can use [prefixedCSS](#modernizr-prefixedcss). 1269 | */ 1270 | var prefixed = ModernizrProto.prefixed = function(prop, obj, elem) { 1271 | if (prop.indexOf('@') === 0) { 1272 | return atRule(prop); 1273 | } 1274 | 1275 | if (prop.indexOf('-') !== -1) { 1276 | // Convert hyphen-case to camelCase 1277 | prop = cssToDOM(prop); 1278 | } 1279 | if (!obj) { 1280 | return testPropsAll(prop, 'pfx'); 1281 | } else { 1282 | // Testing DOM property e.g. Modernizr.prefixed('requestAnimationFrame', window) // 'mozRequestAnimationFrame' 1283 | return testPropsAll(prop, obj, elem); 1284 | } 1285 | }; 1286 | 1287 | 1288 | /*! 1289 | { 1290 | "name": "Filesystem API", 1291 | "property": "filesystem", 1292 | "caniuse": "filesystem", 1293 | "notes": [{ 1294 | "name": "W3C Spec", 1295 | "href": "https://www.w3.org/TR/file-system-api/" 1296 | }], 1297 | "authors": ["Eric Bidelman (@ebidel)"], 1298 | "tags": ["file"], 1299 | "builderAliases": ["file_filesystem"], 1300 | "knownBugs": ["The API will be present in Chrome incognito, but will throw an exception. See crbug.com/93417"] 1301 | } 1302 | !*/ 1303 | 1304 | Modernizr.addTest('filesystem', !!prefixed('requestFileSystem', window)); 1305 | 1306 | /*! 1307 | { 1308 | "name": "placeholder attribute", 1309 | "property": "placeholder", 1310 | "tags": ["forms", "attribute"], 1311 | "builderAliases": ["forms_placeholder"] 1312 | } 1313 | !*/ 1314 | /* DOC 1315 | Tests for placeholder attribute in inputs and textareas 1316 | */ 1317 | 1318 | Modernizr.addTest('placeholder', ('placeholder' in createElement('input') && 'placeholder' in createElement('textarea'))); 1319 | 1320 | /*! 1321 | { 1322 | "name": "Fullscreen API", 1323 | "property": "fullscreen", 1324 | "caniuse": "fullscreen", 1325 | "notes": [{ 1326 | "name": "MDN Docs", 1327 | "href": "https://developer.mozilla.org/en/API/Fullscreen" 1328 | }], 1329 | "polyfills": ["screenfulljs"], 1330 | "builderAliases": ["fullscreen_api"] 1331 | } 1332 | !*/ 1333 | /* DOC 1334 | Detects support for the ability to make the current website take over the user's entire screen 1335 | */ 1336 | 1337 | // github.com/Modernizr/Modernizr/issues/739 1338 | Modernizr.addTest('fullscreen', !!(prefixed('exitFullscreen', document, false) || prefixed('cancelFullScreen', document, false))); 1339 | 1340 | /*! 1341 | { 1342 | "name": "Geolocation API", 1343 | "property": "geolocation", 1344 | "caniuse": "geolocation", 1345 | "tags": ["media"], 1346 | "notes": [{ 1347 | "name": "MDN Docs", 1348 | "href": "https://developer.mozilla.org/en-US/docs/WebAPI/Using_geolocation" 1349 | }], 1350 | "polyfills": [ 1351 | "joshuabell-polyfill", 1352 | "webshims", 1353 | "geo-location-javascript", 1354 | "geolocation-api-polyfill" 1355 | ] 1356 | } 1357 | !*/ 1358 | /* DOC 1359 | Detects support for the Geolocation API for users to provide their location to web applications. 1360 | */ 1361 | 1362 | // geolocation is often considered a trivial feature detect... 1363 | // Turns out, it's quite tricky to get right: 1364 | // 1365 | // Using !!navigator.geolocation does two things we don't want. It: 1366 | // 1. Leaks memory in IE9: github.com/Modernizr/Modernizr/issues/513 1367 | // 2. Disables page caching in WebKit: webk.it/43956 1368 | // 1369 | // Meanwhile, in Firefox < 8, an about:config setting could expose 1370 | // a false positive that would throw an exception: bugzil.la/688158 1371 | 1372 | Modernizr.addTest('geolocation', 'geolocation' in navigator); 1373 | 1374 | 1375 | /** 1376 | * Modernizr.hasEvent() detects support for a given event 1377 | * 1378 | * @memberOf Modernizr 1379 | * @name Modernizr.hasEvent 1380 | * @optionName Modernizr.hasEvent() 1381 | * @optionProp hasEvent 1382 | * @access public 1383 | * @function hasEvent 1384 | * @param {string|*} eventName - the name of an event to test for (e.g. "resize") 1385 | * @param {Element|string} [element=HTMLDivElement] - is the element|document|window|tagName to test on 1386 | * @returns {boolean} 1387 | * @example 1388 | * `Modernizr.hasEvent` lets you determine if the browser supports a supplied event. 1389 | * By default, it does this detection on a div element 1390 | * 1391 | * ```js 1392 | * hasEvent('blur') // true; 1393 | * ``` 1394 | * 1395 | * However, you are able to give an object as a second argument to hasEvent to 1396 | * detect an event on something other than a div. 1397 | * 1398 | * ```js 1399 | * hasEvent('devicelight', window) // true; 1400 | * ``` 1401 | */ 1402 | var hasEvent = (function() { 1403 | 1404 | // Detect whether event support can be detected via `in`. Test on a DOM element 1405 | // using the "blur" event b/c it should always exist. bit.ly/event-detection 1406 | var needsFallback = !('onblur' in docElement); 1407 | 1408 | function inner(eventName, element) { 1409 | 1410 | var isSupported; 1411 | if (!eventName) { return false; } 1412 | if (!element || typeof element === 'string') { 1413 | element = createElement(element || 'div'); 1414 | } 1415 | 1416 | // Testing via the `in` operator is sufficient for modern browsers and IE. 1417 | // When using `setAttribute`, IE skips "unload", WebKit skips "unload" and 1418 | // "resize", whereas `in` "catches" those. 1419 | eventName = 'on' + eventName; 1420 | isSupported = eventName in element; 1421 | 1422 | // Fallback technique for old Firefox - bit.ly/event-detection 1423 | if (!isSupported && needsFallback) { 1424 | if (!element.setAttribute) { 1425 | // Switch to generic element if it lacks `setAttribute`. 1426 | // It could be the `document`, `window`, or something else. 1427 | element = createElement('div'); 1428 | } 1429 | 1430 | element.setAttribute(eventName, ''); 1431 | isSupported = typeof element[eventName] === 'function'; 1432 | 1433 | if (element[eventName] !== undefined) { 1434 | // If property was created, "remove it" by setting value to `undefined`. 1435 | element[eventName] = undefined; 1436 | } 1437 | element.removeAttribute(eventName); 1438 | } 1439 | 1440 | return isSupported; 1441 | } 1442 | return inner; 1443 | })(); 1444 | 1445 | ModernizrProto.hasEvent = hasEvent; 1446 | 1447 | /*! 1448 | { 1449 | "name": "Hashchange event", 1450 | "property": "hashchange", 1451 | "caniuse": "hashchange", 1452 | "tags": ["history"], 1453 | "notes": [{ 1454 | "name": "MDN Docs", 1455 | "href": "https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onhashchange" 1456 | }], 1457 | "polyfills": [ 1458 | "jquery-hashchange", 1459 | "moo-historymanager", 1460 | "jquery-ajaxy", 1461 | "hasher", 1462 | "shistory" 1463 | ] 1464 | } 1465 | !*/ 1466 | /* DOC 1467 | Detects support for the `hashchange` event, fired when the current location fragment changes. 1468 | */ 1469 | 1470 | Modernizr.addTest('hashchange', function() { 1471 | if (hasEvent('hashchange', window) === false) { 1472 | return false; 1473 | } 1474 | 1475 | // documentMode logic from YUI to filter out IE8 Compat Mode 1476 | // which false positives. 1477 | return (document.documentMode === undefined || document.documentMode > 7); 1478 | }); 1479 | 1480 | /*! 1481 | { 1482 | "name": "History API", 1483 | "property": "history", 1484 | "caniuse": "history", 1485 | "tags": ["history"], 1486 | "authors": ["Hay Kranen", "Alexander Farkas"], 1487 | "notes": [{ 1488 | "name": "W3C Spec", 1489 | "href": "https://www.w3.org/TR/html51/browsers.html#the-history-interface" 1490 | }, { 1491 | "name": "MDN Docs", 1492 | "href": "https://developer.mozilla.org/en-US/docs/Web/API/window.history" 1493 | }], 1494 | "polyfills": ["historyjs", "html5historyapi"] 1495 | } 1496 | !*/ 1497 | /* DOC 1498 | Detects support for the History API for manipulating the browser session history. 1499 | */ 1500 | 1501 | Modernizr.addTest('history', function() { 1502 | // Issue #733 1503 | // The stock browser on Android 2.2 & 2.3, and 4.0.x returns positive on history support 1504 | // Unfortunately support is really buggy and there is no clean way to detect 1505 | // these bugs, so we fall back to a user agent sniff :( 1506 | var ua = navigator.userAgent; 1507 | 1508 | // Some browsers allow to have empty userAgent. 1509 | // Therefore, we need to check ua before using "indexOf" on it. 1510 | if(!ua) { 1511 | return false; 1512 | } 1513 | 1514 | // We only want Android 2 and 4.0, stock browser, and not Chrome which identifies 1515 | // itself as 'Mobile Safari' as well, nor Windows Phone (issue #1471). 1516 | if ((ua.indexOf('Android 2.') !== -1 || 1517 | (ua.indexOf('Android 4.0') !== -1)) && 1518 | ua.indexOf('Mobile Safari') !== -1 && 1519 | ua.indexOf('Chrome') === -1 && 1520 | ua.indexOf('Windows Phone') === -1 && 1521 | // Since all documents on file:// share an origin, the History apis are 1522 | // blocked there as well 1523 | location.protocol !== 'file:' 1524 | ) { 1525 | return false; 1526 | } 1527 | 1528 | // Return the regular check 1529 | return (window.history && 'pushState' in window.history); 1530 | }); 1531 | 1532 | 1533 | /** 1534 | * hasOwnProp is a shim for hasOwnProperty that is needed for Safari 2.0 support 1535 | * 1536 | * @author kangax 1537 | * @access private 1538 | * @function hasOwnProp 1539 | * @param {object} object - The object to check for a property 1540 | * @param {string} property - The property to check for 1541 | * @returns {boolean} 1542 | */ 1543 | 1544 | // hasOwnProperty shim by kangax needed for Safari 2.0 support 1545 | var hasOwnProp; 1546 | 1547 | (function() { 1548 | var _hasOwnProperty = ({}).hasOwnProperty; 1549 | /* istanbul ignore else */ 1550 | /* we have no way of testing IE 5.5 or safari 2, 1551 | * so just assume the else gets hit */ 1552 | if (!is(_hasOwnProperty, 'undefined') && !is(_hasOwnProperty.call, 'undefined')) { 1553 | hasOwnProp = function(object, property) { 1554 | return _hasOwnProperty.call(object, property); 1555 | }; 1556 | } 1557 | else { 1558 | hasOwnProp = function(object, property) { /* yes, this can give false positives/negatives, but most of the time we don't care about those */ 1559 | return ((property in object) && is(object.constructor.prototype[property], 'undefined')); 1560 | }; 1561 | } 1562 | })(); 1563 | 1564 | 1565 | 1566 | /** 1567 | * setClasses takes an array of class names and adds them to the root element 1568 | * 1569 | * @access private 1570 | * @function setClasses 1571 | * @param {string[]} classes - Array of class names 1572 | */ 1573 | // Pass in an and array of class names, e.g.: 1574 | // ['no-webp', 'borderradius', ...] 1575 | function setClasses(classes) { 1576 | var className = docElement.className; 1577 | var classPrefix = Modernizr._config.classPrefix || ''; 1578 | 1579 | if (isSVG) { 1580 | className = className.baseVal; 1581 | } 1582 | 1583 | // Change `no-js` to `js` (independently of the `enableClasses` option) 1584 | // Handle classPrefix on this too 1585 | if (Modernizr._config.enableJSClass) { 1586 | var reJS = new RegExp('(^|\\s)' + classPrefix + 'no-js(\\s|$)'); 1587 | className = className.replace(reJS, '$1' + classPrefix + 'js$2'); 1588 | } 1589 | 1590 | if (Modernizr._config.enableClasses) { 1591 | // Add the new classes 1592 | if (classes.length > 0) { 1593 | className += ' ' + classPrefix + classes.join(' ' + classPrefix); 1594 | } 1595 | if (isSVG) { 1596 | docElement.className.baseVal = className; 1597 | } else { 1598 | docElement.className = className; 1599 | } 1600 | } 1601 | } 1602 | 1603 | ; 1604 | 1605 | 1606 | // _l tracks listeners for async tests, as well as tests that execute after the initial run 1607 | ModernizrProto._l = {}; 1608 | 1609 | /** 1610 | * Modernizr.on is a way to listen for the completion of async tests. Being 1611 | * asynchronous, they may not finish before your scripts run. As a result you 1612 | * will get a possibly false negative `undefined` value. 1613 | * 1614 | * @memberOf Modernizr 1615 | * @name Modernizr.on 1616 | * @access public 1617 | * @function on 1618 | * @param {string} feature - String name of the feature detect 1619 | * @param {Function} cb - Callback function returning a Boolean - true if feature is supported, false if not 1620 | * @returns {void} 1621 | * @example 1622 | * 1623 | * ```js 1624 | * Modernizr.on('flash', function( result ) { 1625 | * if (result) { 1626 | * // the browser has flash 1627 | * } else { 1628 | * // the browser does not have flash 1629 | * } 1630 | * }); 1631 | * ``` 1632 | */ 1633 | ModernizrProto.on = function(feature, cb) { 1634 | // Create the list of listeners if it doesn't exist 1635 | if (!this._l[feature]) { 1636 | this._l[feature] = []; 1637 | } 1638 | 1639 | // Push this test on to the listener list 1640 | this._l[feature].push(cb); 1641 | 1642 | // If it's already been resolved, trigger it on next tick 1643 | if (Modernizr.hasOwnProperty(feature)) { 1644 | // Next Tick 1645 | setTimeout(function() { 1646 | Modernizr._trigger(feature, Modernizr[feature]); 1647 | }, 0); 1648 | } 1649 | }; 1650 | 1651 | /** 1652 | * _trigger is the private function used to signal test completion and run any 1653 | * callbacks registered through [Modernizr.on](#modernizr-on) 1654 | * 1655 | * @memberOf Modernizr 1656 | * @name Modernizr._trigger 1657 | * @access private 1658 | * @function _trigger 1659 | * @param {string} feature - string name of the feature detect 1660 | * @param {Function|boolean} [res] - A feature detection function, or the boolean = 1661 | * result of a feature detection function 1662 | * @returns {void} 1663 | */ 1664 | ModernizrProto._trigger = function(feature, res) { 1665 | if (!this._l[feature]) { 1666 | return; 1667 | } 1668 | 1669 | var cbs = this._l[feature]; 1670 | 1671 | // Force async 1672 | setTimeout(function() { 1673 | var i, cb; 1674 | for (i = 0; i < cbs.length; i++) { 1675 | cb = cbs[i]; 1676 | cb(res); 1677 | } 1678 | }, 0); 1679 | 1680 | // Don't trigger these again 1681 | delete this._l[feature]; 1682 | }; 1683 | 1684 | /** 1685 | * addTest allows you to define your own feature detects that are not currently 1686 | * included in Modernizr (under the covers it's the exact same code Modernizr 1687 | * uses for its own [feature detections](https://github.com/Modernizr/Modernizr/tree/master/feature-detects)). 1688 | * Just like the official detects, the result 1689 | * will be added onto the Modernizr object, as well as an appropriate className set on 1690 | * the html element when configured to do so 1691 | * 1692 | * @memberOf Modernizr 1693 | * @name Modernizr.addTest 1694 | * @optionName Modernizr.addTest() 1695 | * @optionProp addTest 1696 | * @access public 1697 | * @function addTest 1698 | * @param {string|object} feature - The string name of the feature detect, or an 1699 | * object of feature detect names and test 1700 | * @param {Function|boolean} test - Function returning true if feature is supported, 1701 | * false if not. Otherwise a boolean representing the results of a feature detection 1702 | * @returns {object} the Modernizr object to allow chaining 1703 | * @example 1704 | * 1705 | * The most common way of creating your own feature detects is by calling 1706 | * `Modernizr.addTest` with a string (preferably just lowercase, without any 1707 | * punctuation), and a function you want executed that will return a boolean result 1708 | * 1709 | * ```js 1710 | * Modernizr.addTest('itsTuesday', function() { 1711 | * var d = new Date(); 1712 | * return d.getDay() === 2; 1713 | * }); 1714 | * ``` 1715 | * 1716 | * When the above is run, it will set Modernizr.itstuesday to `true` when it is tuesday, 1717 | * and to `false` every other day of the week. One thing to notice is that the names of 1718 | * feature detect functions are always lowercased when added to the Modernizr object. That 1719 | * means that `Modernizr.itsTuesday` will not exist, but `Modernizr.itstuesday` will. 1720 | * 1721 | * 1722 | * Since we only look at the returned value from any feature detection function, 1723 | * you do not need to actually use a function. For simple detections, just passing 1724 | * in a statement that will return a boolean value works just fine. 1725 | * 1726 | * ```js 1727 | * Modernizr.addTest('hasjquery', 'jQuery' in window); 1728 | * ``` 1729 | * 1730 | * Just like before, when the above runs `Modernizr.hasjquery` will be true if 1731 | * jQuery has been included on the page. Not using a function saves a small amount 1732 | * of overhead for the browser, as well as making your code much more readable. 1733 | * 1734 | * Finally, you also have the ability to pass in an object of feature names and 1735 | * their tests. This is handy if you want to add multiple detections in one go. 1736 | * The keys should always be a string, and the value can be either a boolean or 1737 | * function that returns a boolean. 1738 | * 1739 | * ```js 1740 | * var detects = { 1741 | * 'hasjquery': 'jQuery' in window, 1742 | * 'itstuesday': function() { 1743 | * var d = new Date(); 1744 | * return d.getDay() === 2; 1745 | * } 1746 | * } 1747 | * 1748 | * Modernizr.addTest(detects); 1749 | * ``` 1750 | * 1751 | * There is really no difference between the first methods and this one, it is 1752 | * just a convenience to let you write more readable code. 1753 | */ 1754 | function addTest(feature, test) { 1755 | 1756 | if (typeof feature === 'object') { 1757 | for (var key in feature) { 1758 | if (hasOwnProp(feature, key)) { 1759 | addTest(key, feature[ key ]); 1760 | } 1761 | } 1762 | } else { 1763 | 1764 | feature = feature.toLowerCase(); 1765 | var featureNameSplit = feature.split('.'); 1766 | var last = Modernizr[featureNameSplit[0]]; 1767 | 1768 | // Again, we don't check for parent test existence. Get that right, though. 1769 | if (featureNameSplit.length === 2) { 1770 | last = last[featureNameSplit[1]]; 1771 | } 1772 | 1773 | if (typeof last !== 'undefined') { 1774 | // we're going to quit if you're trying to overwrite an existing test 1775 | // if we were to allow it, we'd do this: 1776 | // var re = new RegExp("\\b(no-)?" + feature + "\\b"); 1777 | // docElement.className = docElement.className.replace( re, '' ); 1778 | // but, no rly, stuff 'em. 1779 | return Modernizr; 1780 | } 1781 | 1782 | test = typeof test === 'function' ? test() : test; 1783 | 1784 | // Set the value (this is the magic, right here). 1785 | if (featureNameSplit.length === 1) { 1786 | Modernizr[featureNameSplit[0]] = test; 1787 | } else { 1788 | // cast to a Boolean, if not one already 1789 | if (Modernizr[featureNameSplit[0]] && !(Modernizr[featureNameSplit[0]] instanceof Boolean)) { 1790 | Modernizr[featureNameSplit[0]] = new Boolean(Modernizr[featureNameSplit[0]]); 1791 | } 1792 | 1793 | Modernizr[featureNameSplit[0]][featureNameSplit[1]] = test; 1794 | } 1795 | 1796 | // Set a single class (either `feature` or `no-feature`) 1797 | setClasses([(!!test && test !== false ? '' : 'no-') + featureNameSplit.join('-')]); 1798 | 1799 | // Trigger the event 1800 | Modernizr._trigger(feature, test); 1801 | } 1802 | 1803 | return Modernizr; // allow chaining. 1804 | } 1805 | 1806 | // After all the tests are run, add self to the Modernizr prototype 1807 | Modernizr._q.push(function() { 1808 | ModernizrProto.addTest = addTest; 1809 | }); 1810 | 1811 | 1812 | 1813 | /*! 1814 | { 1815 | "name": "Webp", 1816 | "async": true, 1817 | "property": "webp", 1818 | "caniuse": "webp", 1819 | "tags": ["image"], 1820 | "builderAliases": ["img_webp"], 1821 | "authors": ["Krister Kari", "@amandeep", "Rich Bradshaw", "Ryan Seddon", "Paul Irish"], 1822 | "notes": [{ 1823 | "name": "Webp Info", 1824 | "href": "https://developers.google.com/speed/webp/" 1825 | }, { 1826 | "name": "Chromium blog - Chrome 32 Beta: Animated WebP images and faster Chrome for Android touch input", 1827 | "href": "https://blog.chromium.org/2013/11/chrome-32-beta-animated-webp-images-and.html" 1828 | }, { 1829 | "name": "Webp Lossless Spec", 1830 | "href": "https://developers.google.com/speed/webp/docs/webp_lossless_bitstream_specification" 1831 | }, { 1832 | "name": "Article about WebP support", 1833 | "href": "https://optimus.keycdn.com/support/webp-support/" 1834 | }, { 1835 | "name": "Chromium WebP announcement", 1836 | "href": "https://blog.chromium.org/2011/11/lossless-and-transparency-encoding-in.html?m=1" 1837 | }] 1838 | } 1839 | !*/ 1840 | /* DOC 1841 | Tests for lossy, non-alpha webp support. 1842 | 1843 | Tests for all forms of webp support (lossless, lossy, alpha, and animated).. 1844 | 1845 | Modernizr.webp // Basic support (lossy) 1846 | Modernizr.webp.lossless // Lossless 1847 | Modernizr.webp.alpha // Alpha (both lossy and lossless) 1848 | Modernizr.webp.animation // Animated WebP 1849 | 1850 | */ 1851 | 1852 | 1853 | Modernizr.addAsyncTest(function() { 1854 | 1855 | var webpTests = [{ 1856 | 'uri': 'data:image/webp;base64,UklGRiQAAABXRUJQVlA4IBgAAAAwAQCdASoBAAEAAwA0JaQAA3AA/vuUAAA=', 1857 | 'name': 'webp' 1858 | }, { 1859 | 'uri': 'data:image/webp;base64,UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAABBxAR/Q9ERP8DAABWUDggGAAAADABAJ0BKgEAAQADADQlpAADcAD++/1QAA==', 1860 | 'name': 'webp.alpha' 1861 | }, { 1862 | 'uri': 'data:image/webp;base64,UklGRlIAAABXRUJQVlA4WAoAAAASAAAAAAAAAAAAQU5JTQYAAAD/////AABBTk1GJgAAAAAAAAAAAAAAAAAAAGQAAABWUDhMDQAAAC8AAAAQBxAREYiI/gcA', 1863 | 'name': 'webp.animation' 1864 | }, { 1865 | 'uri': 'data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAAAAAAfQ//73v/+BiOh/AAA=', 1866 | 'name': 'webp.lossless' 1867 | }]; 1868 | 1869 | var webp = webpTests.shift(); 1870 | function test(name, uri, cb) { 1871 | 1872 | var image = new Image(); 1873 | 1874 | function addResult(event) { 1875 | // if the event is from 'onload', check the see if the image's width is 1876 | // 1 pixel (which indicates support). otherwise, it fails 1877 | 1878 | var result = event && event.type === 'load' ? image.width === 1 : false; 1879 | var baseTest = name === 'webp'; 1880 | 1881 | // if it is the base test, and the result is false, just set a literal false 1882 | // rather than use the Boolean constructor 1883 | addTest(name, (baseTest && result) ? new Boolean(result) : result); 1884 | 1885 | if (cb) { 1886 | cb(event); 1887 | } 1888 | } 1889 | 1890 | image.onerror = addResult; 1891 | image.onload = addResult; 1892 | 1893 | image.src = uri; 1894 | } 1895 | 1896 | // test for webp support in general 1897 | test(webp.name, webp.uri, function(e) { 1898 | // if the webp test loaded, test everything else. 1899 | if (e && e.type === 'load') { 1900 | for (var i = 0; i < webpTests.length; i++) { 1901 | test(webpTests[i].name, webpTests[i].uri); 1902 | } 1903 | } 1904 | }); 1905 | 1906 | }); 1907 | 1908 | 1909 | /*! 1910 | { 1911 | "name": "Webp Alpha", 1912 | "async": true, 1913 | "property": "webpalpha", 1914 | "aliases": ["webp-alpha"], 1915 | "tags": ["image"], 1916 | "authors": ["Krister Kari", "Rich Bradshaw", "Ryan Seddon", "Paul Irish"], 1917 | "notes": [{ 1918 | "name": "WebP Info", 1919 | "href": "https://developers.google.com/speed/webp/" 1920 | }, { 1921 | "name": "Article about WebP support", 1922 | "href": "https://optimus.keycdn.com/support/webp-support/" 1923 | }, { 1924 | "name": "Chromium WebP announcement", 1925 | "href": "https://blog.chromium.org/2011/11/lossless-and-transparency-encoding-in.html?m=1" 1926 | }] 1927 | } 1928 | !*/ 1929 | /* DOC 1930 | Tests for transparent webp support. 1931 | */ 1932 | 1933 | Modernizr.addAsyncTest(function() { 1934 | var image = new Image(); 1935 | 1936 | image.onerror = function() { 1937 | addTest('webpalpha', false, {aliases: ['webp-alpha']}); 1938 | }; 1939 | 1940 | image.onload = function() { 1941 | addTest('webpalpha', image.width === 1, {aliases: ['webp-alpha']}); 1942 | }; 1943 | 1944 | image.src = 'data:image/webp;base64,UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAABBxAR/Q9ERP8DAABWUDggGAAAADABAJ0BKgEAAQADADQlpAADcAD++/1QAA=='; 1945 | }); 1946 | 1947 | /*! 1948 | { 1949 | "name": "IndexedDB", 1950 | "property": "indexeddb", 1951 | "caniuse": "indexeddb", 1952 | "tags": ["storage"], 1953 | "polyfills": ["indexeddb"], 1954 | "async": true 1955 | } 1956 | !*/ 1957 | /* DOC 1958 | Detects support for the IndexedDB client-side storage API (final spec). 1959 | */ 1960 | 1961 | // Vendors had inconsistent prefixing with the experimental Indexed DB: 1962 | // - Webkit's implementation is accessible through webkitIndexedDB 1963 | // - Firefox shipped moz_indexedDB before FF4b9, but since then has been mozIndexedDB 1964 | // For speed, we don't test the legacy (and beta-only) indexedDB 1965 | 1966 | Modernizr.addAsyncTest(function() { 1967 | 1968 | var indexeddb; 1969 | 1970 | try { 1971 | // Firefox throws a Security Error when cookies are disabled 1972 | indexeddb = prefixed('indexedDB', window); 1973 | } catch (e) { 1974 | } 1975 | 1976 | if (indexeddb) { 1977 | var testDBName = 'modernizr-' + Math.random(); 1978 | var req; 1979 | try { 1980 | req = indexeddb.open(testDBName); 1981 | } catch (e) { 1982 | addTest('indexeddb', false); 1983 | return; 1984 | } 1985 | 1986 | req.onerror = function(event) { 1987 | if (req.error && (req.error.name === 'InvalidStateError' || req.error.name === 'UnknownError')) { 1988 | addTest('indexeddb', false); 1989 | event.preventDefault(); 1990 | } else { 1991 | addTest('indexeddb', true); 1992 | detectDeleteDatabase(indexeddb, testDBName); 1993 | } 1994 | }; 1995 | 1996 | req.onsuccess = function() { 1997 | addTest('indexeddb', true); 1998 | detectDeleteDatabase(indexeddb, testDBName); 1999 | }; 2000 | } else { 2001 | addTest('indexeddb', false); 2002 | } 2003 | }); 2004 | 2005 | function detectDeleteDatabase(indexeddb, testDBName) { 2006 | var deleteReq = indexeddb.deleteDatabase(testDBName); 2007 | deleteReq.onsuccess = function() { 2008 | addTest('indexeddb.deletedatabase', true); 2009 | }; 2010 | deleteReq.onerror = function() { 2011 | addTest('indexeddb.deletedatabase', false); 2012 | }; 2013 | } 2014 | 2015 | ; 2016 | /*! 2017 | { 2018 | "name": "JSON", 2019 | "property": "json", 2020 | "caniuse": "json", 2021 | "notes": [{ 2022 | "name": "MDN Docs", 2023 | "href": "https://developer.mozilla.org/en-US/docs/Glossary/JSON" 2024 | }], 2025 | "polyfills": ["json2"] 2026 | } 2027 | !*/ 2028 | /* DOC 2029 | Detects native support for JSON handling functions. 2030 | */ 2031 | 2032 | // this will also succeed if you've loaded the JSON2.js polyfill ahead of time 2033 | // ... but that should be obvious. :) 2034 | 2035 | Modernizr.addTest('json', 'JSON' in window && 'parse' in JSON && 'stringify' in JSON); 2036 | 2037 | /*! 2038 | { 2039 | "name": "Fetch API", 2040 | "property": "fetch", 2041 | "tags": ["network"], 2042 | "caniuse": "fetch", 2043 | "notes": [{ 2044 | "name": "WHATWG Spec", 2045 | "href": "https://fetch.spec.whatwg.org/" 2046 | }], 2047 | "polyfills": ["fetch"] 2048 | } 2049 | !*/ 2050 | /* DOC 2051 | Detects support for the fetch API, a modern replacement for XMLHttpRequest. 2052 | */ 2053 | 2054 | Modernizr.addTest('fetch', 'fetch' in window); 2055 | 2056 | /*! 2057 | { 2058 | "name": "XML HTTP Request Level 2 XHR2", 2059 | "property": "xhr2", 2060 | "caniuse": "xhr2", 2061 | "tags": ["network"], 2062 | "builderAliases": ["network_xhr2"], 2063 | "notes": [{ 2064 | "name": "W3C Spec", 2065 | "href": "https://www.w3.org/TR/XMLHttpRequest2/" 2066 | }, { 2067 | "name": "Details on Related Github Issue", 2068 | "href": "https://github.com/Modernizr/Modernizr/issues/385" 2069 | }] 2070 | } 2071 | !*/ 2072 | /* DOC 2073 | Tests for XHR2. 2074 | */ 2075 | 2076 | // all three of these details report consistently across all target browsers: 2077 | // !!(window.ProgressEvent); 2078 | // 'XMLHttpRequest' in window && 'withCredentials' in new XMLHttpRequest 2079 | Modernizr.addTest('xhr2', 'XMLHttpRequest' in window && 'withCredentials' in new XMLHttpRequest()); 2080 | 2081 | /*! 2082 | { 2083 | "name": "Notification", 2084 | "property": "notification", 2085 | "caniuse": "notifications", 2086 | "authors": ["Theodoor van Donge", "Hendrik Beskow"], 2087 | "notes": [{ 2088 | "name": "HTML5 Rocks Tutorial", 2089 | "href": "https://www.html5rocks.com/en/tutorials/notifications/quick/" 2090 | }, { 2091 | "name": "W3C Spec", 2092 | "href": "https://www.w3.org/TR/notifications/" 2093 | }, { 2094 | "name": "Changes in Chrome to Notifications API due to Service Worker Push Notifications", 2095 | "href": "https://developers.google.com/web/updates/2015/05/Notifying-you-of-notificiation-changes" 2096 | }], 2097 | "knownBugs": ["Possibility of false-positive on Chrome for Android if permissions we're granted for a website prior to Chrome 44."], 2098 | "polyfills": ["desktop-notify", "html5-notifications"] 2099 | } 2100 | !*/ 2101 | /* DOC 2102 | Detects support for the Notifications API 2103 | */ 2104 | 2105 | Modernizr.addTest('notification', function() { 2106 | if (!window.Notification || !window.Notification.requestPermission) { 2107 | return false; 2108 | } 2109 | // if permission is already granted, assume support 2110 | if (window.Notification.permission === 'granted') { 2111 | return true; 2112 | } 2113 | 2114 | try { 2115 | new window.Notification(''); 2116 | } catch (e) { 2117 | if (e.name === 'TypeError') { 2118 | return false; 2119 | } 2120 | } 2121 | 2122 | return true; 2123 | }); 2124 | 2125 | /*! 2126 | { 2127 | "name": "Navigation Timing API", 2128 | "property": "performance", 2129 | "caniuse": "nav-timing", 2130 | "tags": ["performance"], 2131 | "authors": ["Scott Murphy (@uxder)"], 2132 | "notes": [{ 2133 | "name": "W3C Spec", 2134 | "href": "https://www.w3.org/TR/navigation-timing/" 2135 | }, { 2136 | "name": "HTML5 Rocks Tutorial", 2137 | "href": "https://www.html5rocks.com/en/tutorials/webperformance/basics/" 2138 | }], 2139 | "polyfills": ["perfnow"] 2140 | } 2141 | !*/ 2142 | /* DOC 2143 | Detects support for the Navigation Timing API, for measuring browser and connection performance. 2144 | */ 2145 | 2146 | Modernizr.addTest('performance', !!prefixed('performance', window)); 2147 | 2148 | 2149 | /** 2150 | * List of JavaScript DOM values used for tests including a NON-prefix 2151 | * 2152 | * @memberOf Modernizr 2153 | * @name Modernizr._domPrefixesAll 2154 | * @optionName Modernizr._domPrefixesAll 2155 | * @optionProp domPrefixesAll 2156 | * @access public 2157 | * @example 2158 | * 2159 | * Modernizr._domPrefixesAll is exactly the same as [_domPrefixes](#modernizr-_domPrefixes), but also 2160 | * adds an empty string in the array to test for a non-prefixed value 2161 | * 2162 | * ```js 2163 | * Modernizr._domPrefixesAll === [ "", "Moz", "O", "ms", "Webkit" ]; 2164 | * ``` 2165 | */ 2166 | var domPrefixesAll = [''].concat(domPrefixes); 2167 | ModernizrProto._domPrefixesAll = domPrefixesAll; 2168 | 2169 | /*! 2170 | { 2171 | "name": "DOM Pointer Events API", 2172 | "property": "pointerevents", 2173 | "caniuse": "pointer", 2174 | "tags": ["input"], 2175 | "authors": ["Stu Cox"], 2176 | "notes": [{ 2177 | "name": "W3C Spec (Pointer Events)", 2178 | "href": "https://www.w3.org/TR/pointerevents/" 2179 | }, { 2180 | "name": "W3C Spec (Pointer Events Level 2)", 2181 | "href": "https://www.w3.org/TR/pointerevents2/" 2182 | }, { 2183 | "name": "MDN Docs", 2184 | "href": "https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent" 2185 | }], 2186 | "warnings": ["This property name now refers to W3C DOM PointerEvents: https://github.com/Modernizr/Modernizr/issues/548#issuecomment-12812099"], 2187 | "polyfills": ["pep"] 2188 | } 2189 | !*/ 2190 | /* DOC 2191 | 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. 2192 | */ 2193 | 2194 | // **Test name hijacked!** 2195 | // Now refers to W3C DOM PointerEvents spec rather than the CSS pointer-events property. 2196 | Modernizr.addTest('pointerevents', function() { 2197 | // Cannot use `.prefixed()` for events, so test each prefix 2198 | for (var i = 0, len = domPrefixesAll.length; i < len; i++) { 2199 | if (hasEvent(domPrefixesAll[i] + 'pointerdown')) { 2200 | return true; 2201 | } 2202 | } 2203 | return false; 2204 | }); 2205 | 2206 | /*! 2207 | { 2208 | "name": "QuerySelector", 2209 | "property": "queryselector", 2210 | "caniuse": "queryselector", 2211 | "tags": ["queryselector"], 2212 | "authors": ["Andrew Betts (@triblondon)"], 2213 | "notes": [{ 2214 | "name": "W3C Spec", 2215 | "href": "https://www.w3.org/TR/selectors-api/#queryselectorall" 2216 | }], 2217 | "polyfills": ["css-selector-engine"] 2218 | } 2219 | !*/ 2220 | /* DOC 2221 | Detects support for querySelector. 2222 | */ 2223 | 2224 | Modernizr.addTest('queryselector', 'querySelector' in document && 'querySelectorAll' in document); 2225 | 2226 | /*! 2227 | { 2228 | "name": "script[async]", 2229 | "property": "scriptasync", 2230 | "caniuse": "script-async", 2231 | "tags": ["script"], 2232 | "builderAliases": ["script_async"], 2233 | "authors": ["Theodoor van Donge"] 2234 | } 2235 | !*/ 2236 | /* DOC 2237 | Detects support for the `async` attribute on the `