/*! * Modernizr JavaScript library 1.1 * http://modernizr.com/ * * Copyright (c) 2009 Faruk Ates - http://farukat.es/ * Licensed under the MIT license. * http://modernizr.com/license/ * * Featuring major contributions by * Paul Irish - http://paulirish.com * Ben Alman - http://benalman.com/ */ /* * Modernizr is a script that will detect native CSS3 and HTML5 features * available in the current UA and provide an object containing all * features with a true/false value, depending on whether the UA has * native support for it or not. * * In addition to that, Modernizr will add classes to the * element of the page, one for each cutting-edge feature. If the UA * supports it, a class like "cssgradients" will be added. If not, * the class name will be "no-cssgradients". This allows for simple * if-conditionals in CSS styling, making it easily to have fine * control over the look and feel of your website. * * @author Faruk Ates * @copyright (2009) Faruk Ates. * * @contributor Paul Irish * @contributor Ben Alman */ window.Modernizr = (function(window,doc){ var version = '1.1', ret = {}, /** * enableHTML5 is a private property for advanced use only. If enabled, * it will make Modernizr.init() run through a brief while() loop in * which it will create all HTML5 elements in the DOM to allow for * styling them in Internet Explorer, which does not recognize any * non-HTML4 elements unless created in the DOM this way. * * enableHTML5 is ON by default. */ enableHTML5 = true, /** * enableNoClasses is a private property that, when enabled, will * add classnames to the element at all times, but prefixes * failed groups with "no-", e.g. "no-cssanimations". * This allows for very easy IF / ELSE style rules in your CSS. It * can be disabled if these "no-classes" are not needed or desired. * * enableNoClasses is ON by default. */ enableNoClasses = true, /** * fontfaceCheckDelay is the ms delay before the @font-face test is * checked a second time. This is neccessary because both Gecko and * WebKit do not load data: URI font data synchronously. * https://bugzilla.mozilla.org/show_bug.cgi?id=512566 * If you need to query for @font-face support, send a callback to: * Modernizr._fontfaceready(fn); * The callback is passed the boolean value of Modernizr.fontface */ fontfaceCheckDelay = 100, docElement = doc.documentElement, /** * Create our "modernizr" element that we do most feature tests on. */ m = doc.createElement( 'modernizr' ), m_style = m.style, /** * Create the input element for various Web Forms feature tests. */ f = doc.createElement( 'input' ), // Reused strings. canvas = 'canvas', canvastext = 'canvastext', rgba = 'rgba', hsla = 'hsla', multiplebgs = 'multiplebgs', borderimage = 'borderimage', borderradius = 'borderradius', boxshadow = 'boxshadow', opacity = 'opacity', cssanimations = 'cssanimations', csscolumns = 'csscolumns', cssgradients = 'cssgradients', cssreflections = 'cssreflections', csstransforms = 'csstransforms', csstransforms3d = 'csstransforms3d', csstransitions = 'csstransitions', fontface = 'fontface', geolocation = 'geolocation', video = 'video', audio = 'audio', input = 'input', inputtypes = input + 'types', // inputtypes is an object of its own containing individual tests for // various new input types, such as search, range, datetime, etc. // SVG is not yet supported in Modernizr // svg = 'svg', background = 'background', backgroundColor = background + 'Color', canPlayType = 'canPlayType', localStorage = 'localstorage', sessionStorage = 'sessionstorage', webWorkers = 'webworkers', applicationCache = 'applicationcache', // list of property values to set for css tests. see ticket #21 setProperties = ' -o- -moz- -ms- -webkit- '.split(' '), tests = {}, inputs = {}, attrs = {}, elems, elem, i, feature, classes = []; /** * set_css applies given styles to the Modernizr DOM node. */ function set_css( str ) { m_style.cssText = str; } /** * set_css_all extrapolates all vendor-specific css strings. */ function set_css_all( str1, str2 ) { return set_css(setProperties.join(str1 + ';') + ( str2 || '' )); } /** * contains returns a boolean for if substr is found within str. */ function contains( str, substr ) { return str.indexOf( substr ) !== -1; } /** * test_props is a generic CSS / DOM property test; if a browser supports * a certain property, it won't return undefined for it. * A supported CSS property returns empty string when its not yet set. */ function test_props( props, callback ) { for ( var i in props ) { if ( m_style[ props[i] ] !== undefined && ( !callback || callback( props[i] ) ) ) { return true; } } } /** * test_props_all tests a list of DOM properties we want to check against. * We specify literally ALL possible (known and/or likely) properties on * the element including the non-vendor prefixed one, for forward- * compatibility. */ function test_props_all( prop, callback ) { var uc_prop = prop.charAt(0).toUpperCase() + prop.substr(1), props = [ prop, 'webkit' + uc_prop, 'Moz' + uc_prop, 'moz' + uc_prop, 'o' + uc_prop, 'ms' + uc_prop ]; return !!test_props( props, callback ); } // Tests /** * Canvas tests in Modernizr 1.0 are still somewhat rudimentary. However, * the added "canvastext" test allows for a slightly more reliable and * usable setup. */ tests[canvas] = function() { return !!doc.createElement( canvas ).getContext; }; tests[canvastext] = function() { return !!(tests[canvas]() && typeof doc.createElement( canvas ).getContext('2d').fillText == 'function'); }; /** * geolocation tests for the new Geolocation API specification. * This test is a standards compliant-only test; for more complete * testing, including a Google Gears fallback, please see: * http://code.google.com/p/geo-location-javascript/ */ tests[geolocation] = function() { return !!navigator.geolocation; }; tests[rgba] = function() { // Set an rgba() color and check the returned value set_css( background + '-color:rgba(150,255,150,.5)' ); return contains( m_style[backgroundColor], rgba ); }; tests[hsla] = function() { // Same as rgba(), in fact, browsers re-map hsla() to rgba() internally set_css( background + '-color:hsla(120,40%,100%,.5)' ); return contains( m_style[backgroundColor], rgba ); }; tests[multiplebgs] = function() { // Setting multiple images AND a color on the background shorthand property // and then querying the style.background property value for the number of // occurrences of "url(" is a reliable method for detecting ACTUAL support for this! set_css( background + ':url(m.png),url(a.png),#f99 url(m.png)' ); // If the UA supports multiple backgrounds, there should be three occurrences // of the string "url(" in the return value for elem_style.background return /(url\s*\(.*?){3}/.test(m_style[background]); }; // In testing support for a given CSS property, it's legit to test: // elem.style[styleName] !== undefined // If the property is supported it will return an empty string, // if unsupported it will return undefined. // We'll take advantage of this quick test and skip setting a style // on our modernizr element, but instead just testing undefined vs // empty string. // The legacy set_css_all calls will remain in the source // (however, commented) in for clarity, yet functionally they are // no longer needed. tests[borderimage] = function() { // set_css_all( 'border-image:url(m.png) 1 1 stretch' ); return test_props_all( 'borderImage' ); }; tests[borderradius] = function() { // set_css_all( 'border-radius:10px' ); return test_props_all( 'borderRadius', '', function( prop ) { return contains( prop, 'orderRadius' ); }); }; tests[boxshadow] = function() { // set_css_all( 'box-shadow:#000 1px 1px 3px' ); return test_props_all( 'boxShadow' ); }; tests[opacity] = function() { // Browsers that actually have CSS Opacity implemented have done so // according to spec, which means their return values are within the // range of [0.0,1.0] - including the leading zero. set_css( 'opacity:.5' ); return contains( m_style[opacity], '0.5' ); }; tests[cssanimations] = function() { // set_css_all( 'animation:"animate" 2s ease 2', 'position:relative' ); return test_props_all( 'animationName' ); }; tests[csscolumns] = function() { // set_css_all( 'column-count:3' ); return test_props_all( 'columnCount' ); }; tests[cssgradients] = function() { /** * For CSS Gradients syntax, please see: * http://webkit.org/blog/175/introducing-css-gradients/ * https://developer.mozilla.org/en/CSS/-moz-linear-gradient * https://developer.mozilla.org/en/CSS/-moz-radial-gradient * http://dev.w3.org/csswg/css3-images/#gradients- */ var str1 = background + '-image:', str2 = 'gradient(linear,left top,right bottom,from(#9f9),to(white));', str3 = 'linear-gradient(left top,#9f9, white);'; set_css( str1 + str2 + str1 + '-webkit-' + str2 + str1 + '-moz-' + str2 + str1 + '-o-' + str2 + str1 + '-ms-' + str2 + str1 + str3 + str1 + '-webkit-' + str3 + str1 + '-moz-' + str3 + str1 + '-o-' + str3 + str1 + '-ms-' + str3 ); return contains( m_style.backgroundImage, 'gradient' ); }; tests[cssreflections] = function() { // set_css_all( 'box-reflect:right 1px' ); return test_props_all( 'boxReflect' ); }; tests[csstransforms] = function() { // set_css_all( 'transform:rotate(3deg)' ); return !!test_props([ 'transformProperty', 'webkitTransform', 'MozTransform', 'mozTransform', 'oTransform', 'msTransform' ]); }; tests[csstransforms3d] = function() { // set_css_all( 'perspective:500' ); return !!test_props([ 'perspectiveProperty', 'webkitPerspective', 'MozPerspective', 'mozPerspective', 'oPerspective', 'msPerspective' ]); }; tests[csstransitions] = function() { // set_css_all( 'transition:all .5s linear' ); return test_props_all( 'transitionProperty' ); }; // @font-face detection routine created by Paul Irish - paulirish.com // Merged into Modernizr with approval. Read more about Paul's work here: // http://paulirish.com/2009/font-face-feature-detection/ tests[fontface] = (function(){ var fontret; if (!(!/*@cc_on@if(@_jscript_version>=5)!@end@*/0)) fontret = true; else { // Create variables for dedicated @font-face test var st = doc.createElement('style'), spn = doc.createElement('span'), wid, nwid, isFakeBody = false, body = doc.body, callback, isCallbackCalled; // The following is a font-face + glyph definition for the . character: st.textContent = "@font-face{font-family:testfont;src:url('data:font/ttf;base64,AAEAAAAMAIAAAwBAT1MvMliohmwAAADMAAAAVmNtYXCp5qrBAAABJAAAANhjdnQgACICiAAAAfwAAAAEZ2FzcP//AAMAAAIAAAAACGdseWYv5OZoAAACCAAAANxoZWFk69bnvwAAAuQAAAA2aGhlYQUJAt8AAAMcAAAAJGhtdHgGDgC4AAADQAAAABRsb2NhAIQAwgAAA1QAAAAMbWF4cABVANgAAANgAAAAIG5hbWUgXduAAAADgAAABPVwb3N03NkzmgAACHgAAAA4AAECBAEsAAUAAAKZAswAAACPApkCzAAAAesAMwEJAAACAAMDAAAAAAAAgAACbwAAAAoAAAAAAAAAAFBmRWQAAAAgqS8DM/8zAFwDMwDNAAAABQAAAAAAAAAAAAMAAAADAAAAHAABAAAAAABGAAMAAQAAAK4ABAAqAAAABgAEAAEAAgAuqQD//wAAAC6pAP///9ZXAwAAAAAAAAACAAAABgBoAAAAAAAvAAEAAAAAAAAAAAAAAAAAAAABAAIAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEACoAAAAGAAQAAQACAC6pAP//AAAALqkA////1lcDAAAAAAAAAAIAAAAiAogAAAAB//8AAgACACIAAAEyAqoAAwAHAC6xAQAvPLIHBADtMrEGBdw8sgMCAO0yALEDAC88sgUEAO0ysgcGAfw8sgECAO0yMxEhESczESMiARDuzMwCqv1WIgJmAAACAFUAAAIRAc0ADwAfAAATFRQWOwEyNj0BNCYrASIGARQGKwEiJj0BNDY7ATIWFX8aIvAiGhoi8CIaAZIoN/43KCg3/jcoAWD0JB4eJPQkHh7++EY2NkbVRjY2RgAAAAABAEH/+QCdAEEACQAANjQ2MzIWFAYjIkEeEA8fHw8QDxwWFhwWAAAAAQAAAAIAAIuYbWpfDzz1AAsEAAAAAADFn9IuAAAAAMWf0i797/8zA4gDMwAAAAgAAgAAAAAAAAABAAADM/8zAFwDx/3v/98DiAABAAAAAAAAAAAAAAAAAAAABQF2ACIAAAAAAVUAAAJmAFUA3QBBAAAAKgAqACoAWgBuAAEAAAAFAFAABwBUAAQAAgAAAAEAAQAAAEAALgADAAMAAAAQAMYAAQAAAAAAAACLAAAAAQAAAAAAAQAhAIsAAQAAAAAAAgAFAKwAAQAAAAAAAwBDALEAAQAAAAAABAAnAPQAAQAAAAAABQAKARsAAQAAAAAABgAmASUAAQAAAAAADgAaAUsAAwABBAkAAAEWAWUAAwABBAkAAQBCAnsAAwABBAkAAgAKAr0AAwABBAkAAwCGAscAAwABBAkABABOA00AAwABBAkABQAUA5sAAwABBAkABgBMA68AAwABBAkADgA0A/tDb3B5cmlnaHQgMjAwOSBieSBEYW5pZWwgSm9obnNvbi4gIFJlbGVhc2VkIHVuZGVyIHRoZSB0ZXJtcyBvZiB0aGUgT3BlbiBGb250IExpY2Vuc2UuIEtheWFoIExpIGdseXBocyBhcmUgcmVsZWFzZWQgdW5kZXIgdGhlIEdQTCB2ZXJzaW9uIDMuYmFlYzJhOTJiZmZlNTAzMiAtIHN1YnNldCBvZiBKdXJhTGlnaHRiYWVjMmE5MmJmZmU1MDMyIC0gc3Vic2V0IG9mIEZvbnRGb3JnZSAyLjAgOiBKdXJhIExpZ2h0IDogMjMtMS0yMDA5YmFlYzJhOTJiZmZlNTAzMiAtIHN1YnNldCBvZiBKdXJhIExpZ2h0VmVyc2lvbiAyIGJhZWMyYTkyYmZmZTUwMzIgLSBzdWJzZXQgb2YgSnVyYUxpZ2h0aHR0cDovL3NjcmlwdHMuc2lsLm9yZy9PRkwAQwBvAHAAeQByAGkAZwBoAHQAIAAyADAAMAA5ACAAYgB5ACAARABhAG4AaQBlAGwAIABKAG8AaABuAHMAbwBuAC4AIAAgAFIAZQBsAGUAYQBzAGUAZAAgAHUAbgBkAGUAcgAgAHQAaABlACAAdABlAHIAbQBzACAAbwBmACAAdABoAGUAIABPAHAAZQBuACAARgBvAG4AdAAgAEwAaQBjAGUAbgBzAGUALgAgAEsAYQB5AGEAaAAgAEwAaQAgAGcAbAB5AHAAaABzACAAYQByAGUAIAByAGUAbABlAGEAcwBlAGQAIAB1AG4AZABlAHIAIAB0AGgAZQAgAEcAUABMACAAdgBlAHIAcwBpAG8AbgAgADMALgBiAGEAZQBjADIAYQA5ADIAYgBmAGYAZQA1ADAAMwAyACAALQAgAHMAdQBiAHMAZQB0ACAAbwBmACAASgB1AHIAYQBMAGkAZwBoAHQAYgBhAGUAYwAyAGEAOQAyAGIAZgBmAGUANQAwADMAMgAgAC0AIABzAHUAYgBzAGUAdAAgAG8AZgAgAEYAbwBuAHQARgBvAHIAZwBlACAAMgAuADAAIAA6ACAASgB1AHIAYQAgAEwAaQBnAGgAdAAgADoAIAAyADMALQAxAC0AMgAwADAAOQBiAGEAZQBjADIAYQA5ADIAYgBmAGYAZQA1ADAAMwAyACAALQAgAHMAdQBiAHMAZQB0ACAAbwBmACAASgB1AHIAYQAgAEwAaQBnAGgAdABWAGUAcgBzAGkAbwBuACAAMgAgAGIAYQBlAGMAMgBhADkAMgBiAGYAZgBlADUAMAAzADIAIAAtACAAcwB1AGIAcwBlAHQAIABvAGYAIABKAHUAcgBhAEwAaQBnAGgAdABoAHQAdABwADoALwAvAHMAYwByAGkAcAB0AHMALgBzAGkAbAAuAG8AcgBnAC8ATwBGAEwAAAAAAgAAAAAAAP+BADMAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAAQACAQIAEQt6ZXJva2F5YWhsaQ==')}"; doc.getElementsByTagName('head')[0].appendChild(st); spn.setAttribute('style','font:99px _,serif;position:absolute;visibility:hidden'); if (!body){ body = docElement.appendChild(doc.createElement(fontface)); isFakeBody = true; } // the data-uri'd font only has the . glyph; which is 3 pixels wide. spn.innerHTML = '........'; spn.id = 'fonttest'; body.appendChild(spn); wid = spn.offsetWidth; spn.style.font = '99px testfont,_,serif'; // needed for the CSSFontFaceRule false positives (ff3, chrome, op9) fontret = wid !== spn.offsetWidth; var delayedCheck = function(){ fontret = ret[fontface] = wid !== spn.offsetWidth; docElement.className = docElement.className.replace(/(no-)?font.*?\b/,'') + (fontret ? ' ' : ' no-') + fontface; callback && (isCallbackCalled = true) && callback(fontret); isFakeBody && setTimeout(function(){body.parentNode.removeChild(body)}, 50); } setTimeout(delayedCheck,fontfaceCheckDelay); } // allow for a callback ret._fontfaceready = function(fn){ (isCallbackCalled || fontret) ? fn(fontret) : (callback = fn); } return function(){ return fontret || wid !== spn.offsetWidth; }; })(); // These tests evaluate support of the video/audio elements, as well as // testing what types of content they support. // // we're using the Boolean constructor here, so that we can extend the value // e.g. Modernizr.video // true // Modernizr.video.ogg // 'probably' // // codec values from : http://www.w3.org/TR/html5/video.html#the-source-element // http://www.ietf.org/rfc/rfc4281.txt tests[video] = function() { var elem = doc.createElement(video), bool = !!elem[canPlayType]; if (bool){ bool = new Boolean(bool); bool.ogg = elem[canPlayType]('video/ogg; codecs="theora, vorbis"'); bool.h264 = elem[canPlayType]('video/mp4; codecs="avc1.42E01E, mp4a.40.2"'); } return bool; }; tests[audio] = function() { var elem = doc.createElement(audio), bool = !!elem[canPlayType]; if (bool){ bool = new Boolean(bool); bool.ogg = elem[canPlayType]('audio/ogg; codecs="vorbis"'); bool.mp3 = elem[canPlayType]('audio/mpeg3;'); // mimetypes accepted: // https://developer.mozilla.org/En/Media_formats_supported_by_the_audio_and_video_elements // http://developer.apple.com/safari/library/documentation/appleapplications/reference/SafariWebContent/CreatingContentforSafarioniPhone/CreatingContentforSafarioniPhone.html#//apple_ref/doc/uid/TP40006482-SW7 bool.wav = elem[canPlayType]('audio/wav; codecs="1"'); bool.m4a = elem[canPlayType]('audio/x-m4a;'); } return bool; }; // both localStorage and sessionStorage are // tested in this method because otherwise Firefox will // throw an error: https://bugzilla.mozilla.org/show_bug.cgi?id=365772 // if cookies are disabled tests[localStorage] = function() { return 'localStorage' in window; }; tests[sessionStorage] = function() { return 'sessionStorage' in window; }; tests[webWorkers] = function () { return !!window.Worker; }; tests[applicationCache] = function() { return !!window.applicationCache; }; // Run through all tests and detect their support in the current UA. for ( feature in tests ) { if ( tests.hasOwnProperty( feature ) ) { classes.push( ( !( ret[ feature ] = tests[ feature ]() ) && enableNoClasses ? 'no-' : '' ) + feature ); } } /** * Addtest allows the user to define their own feature tests * the result will be added onto the Modernizr object, * as well as an appropriate className set on the html element * * @param feature - String naming the feature * @param test - Function returning true if feature is supported, false if not */ ret.addTest = function (feature, test) { if (this.hasOwnProperty( feature )) { // warn that feature test is already present } test = !!(test()); docElement.className += ' ' + (!test && enableNoClasses ? 'no-' : '') + feature; ret[ feature ] = test; }; // Run through HTML5's new input attributes to see if the UA understands any. // We're using f which is the element created early on // Mike Taylr has created a comprehensive resource for testing these attributes // when applied to all input types: // http://miketaylr.com/code/input-type-attr.html // spec: http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html#input-type-attr-summary ret[input] = (function(props) { for ( var i in props ) { attrs[ props[i] ] = !!(props[i] in f); } return attrs; })('autocomplete autofocus list placeholder max min multiple pattern required step'.split(' ')); // Run through HTML5's new input types to see if the UA understands any. // This is put behind the tests runloop because it doesn't return a // true/false like all the other tests; instead, it returns an object // containing each input type with its corresponding true/false value ret[inputtypes] = (function(props) { for ( var i in props ) { f.setAttribute('type', props[i]); inputs[ props[i] ] = !!( f.type !== 'text'); } return inputs; })('search tel url email datetime date month week time datetime-local number range color'.split(' ')); /** * Reset m.style.cssText to nothing to reduce memory footprint. */ set_css( '' ); m = f = null; // Enable HTML 5 elements for styling in IE: if ( enableHTML5 && !(!/*@cc_on!@*/0) ) { elems = 'abbr article aside audio canvas datalist details eventsource figure footer header hgroup mark menu meter nav output progress section time video'.split(' '); i = elems.length+1; while ( --i ) { elem = doc.createElement( elems[i] ); } elem = null; } // Assign private properties to the return object with prefix ret._enableHTML5 = enableHTML5; ret._enableNoClasses = enableNoClasses; ret._version = version; // Remove "no-js" class from element, if it exists: (function(H,C){H[C]=H[C].replace(/\bno-js\b/,'js')})(docElement,'className'); // Add the new classes to the element. docElement.className += ' ' + classes.join( ' ' ); return ret; })(this,this.document);