diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..71de6d6559 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +; editorconfig.org +root = true + +; Unix-style newlines +[*] +end_of_line = LF +indent_style = space +indent_size = 2 diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..5961cb48b2 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,12 @@ +language: node_js +node_js: + - 0.7 +notifications: + email: false +before_script: + - "export DISPLAY=:99.0" + - "sh -e /etc/init.d/xvfb start" + - npm install connect + - sudo node test/js/server.js & + - sleep 5 +script: phantomjs test/qunit/run-qunit.js 'http://localhost:80/test/index.html?filter=!caniuse.com' \ No newline at end of file diff --git a/compress.sh b/compress.sh deleted file mode 100755 index f94798b2de..0000000000 --- a/compress.sh +++ /dev/null @@ -1,15 +0,0 @@ -#/bin/sh - -# sudo npm install -g uglify-js - -IN=modernizr.js -OUT=modernizr.min.js - -SIZE_MIN=$(uglifyjs "$IN" --extra --unsafe | tee "$OUT" | wc -c) -SIZE_GZIP=$(gzip -nfc --best "$OUT" | wc -c) - -echo $SIZE_MIN bytes minified, $SIZE_GZIP bytes gzipped - -if [ "$1" == "--test" ]; then - rm "$OUT" -fi diff --git a/feature-detects/battery-level.js b/feature-detects/battery-level.js index 0cae1448b3..07f9575681 100644 --- a/feature-detects/battery-level.js +++ b/feature-detects/battery-level.js @@ -1,11 +1,11 @@ // Low Battery Level // Enable a developer to remove CPU intensive CSS/JS when battery is low -// https://developer.mozilla.org/en/DOM/window.navigator.mozBattery +// developer.mozilla.org/en/DOM/window.navigator.mozBattery // By: Paul Sayre Modernizr.addTest('lowbattery', function () { var minLevel = 0.20, battery = Modernizr.prefixed('battery', navigator); return !!(battery && !battery.charging && battery.level <= minLevel); -}); \ No newline at end of file +}); diff --git a/feature-detects/blob-constructor.js b/feature-detects/blob-constructor.js new file mode 100644 index 0000000000..f10bd7b5f6 --- /dev/null +++ b/feature-detects/blob-constructor.js @@ -0,0 +1,10 @@ +// Blob constructor +// http://dev.w3.org/2006/webapi/FileAPI/#constructorBlob + +Modernizr.addTest('blobconstructor', function () { + try { + return !!new Blob(); + } catch (e) { + return false; + } +}); diff --git a/feature-detects/canvas-todataurl-type.js b/feature-detects/canvas-todataurl-type.js index 5c8ce01649..bf8f2e34ea 100644 --- a/feature-detects/canvas-todataurl-type.js +++ b/feature-detects/canvas-todataurl-type.js @@ -24,5 +24,5 @@ }); }; - image.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==' + image.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg=='; }()); diff --git a/feature-detects/contenteditable.js b/feature-detects/contenteditable.js index c8cfa56703..6543666e55 100644 --- a/feature-detects/contenteditable.js +++ b/feature-detects/contenteditable.js @@ -1,4 +1,9 @@ // contentEditable // http://www.whatwg.org/specs/web-apps/current-work/multipage/editing.html#contenteditable -// by Addy Osmani -Modernizr.addTest('contenteditable', 'isContentEditable' in document.documentElement); + +// this is known to false positive in some mobile browsers +// here is a whitelist of verified working browsers: +// https://github.com/NielsLeenheer/html5test/blob/549f6eac866aa861d9649a0707ff2c0157895706/scripts/engine.js#L2083 + +Modernizr.addTest('contenteditable', + 'contentEditable' in document.documentElement); diff --git a/feature-detects/cors.js b/feature-detects/cors.js index be363c5423..5bf26a9836 100644 --- a/feature-detects/cors.js +++ b/feature-detects/cors.js @@ -1,3 +1,3 @@ // cors // By Theodoor van Donge -Modernizr.addTest('cors', 'withCredentials' in new XMLHttpRequest); \ No newline at end of file +Modernizr.addTest('cors', 'withCredentials' in new XMLHttpRequest()); \ No newline at end of file diff --git a/feature-detects/css-backgroundcliptext.js b/feature-detects/css-backgroundcliptext.js deleted file mode 100644 index ccd76a0475..0000000000 --- a/feature-detects/css-backgroundcliptext.js +++ /dev/null @@ -1,13 +0,0 @@ - -// we can probably retire testing all prefixes for this one soon -// https://developer.mozilla.org/en/CSS/background-clip - -// http://css-tricks.com/7423-transparent-borders-with-background-clip/ - -Modernizr.addTest('backgroundcliptext', function(){ - - var div = document.createElement('div'); - div.style.cssText = Modernizr._prefixes.join('background-clip:text;'); - return !!div.style.cssText.replace(/\s/g,'').length; - -}); diff --git a/feature-detects/css-backgroundposition-fourvalues.js b/feature-detects/css-backgroundposition-fourvalues.js new file mode 100644 index 0000000000..e0f2fa557a --- /dev/null +++ b/feature-detects/css-backgroundposition-fourvalues.js @@ -0,0 +1,19 @@ +/* + https://developer.mozilla.org/en/CSS/background-position + http://www.w3.org/TR/css3-background/#background-position + + Example: http://jsfiddle.net/Blink/bBXvt/ +*/ + +(function() { + + var elem = document.createElement('a'), + eStyle = elem.style, + val = "right 10px bottom 10px"; + + Modernizr.addTest('bgpositionfourvalues', function(){ + eStyle.cssText = "background-position: " + val + ";"; + return (eStyle.backgroundPosition === val); + }); + +}()); diff --git a/feature-detects/css-backgroundposition-xy.js b/feature-detects/css-backgroundposition-xy.js new file mode 100644 index 0000000000..d52d6001fd --- /dev/null +++ b/feature-detects/css-backgroundposition-xy.js @@ -0,0 +1,15 @@ +/* + Allan Lei https://github.com/allanlei + + Check adapted from https://github.com/brandonaaron/jquery-cssHooks/blob/master/bgpos.js + + Test: http://jsfiddle.net/allanlei/R8AYS/ +*/ +Modernizr.addTest('bgpositionxy', function() { + return Modernizr.testStyles('#modernizr {background-position: 3px 5px;}', function(elem) { + var cssStyleDeclaration = window.getComputedStyle ? getComputedStyle(elem, null) : elem.currentStyle; + var xSupport = (cssStyleDeclaration.backgroundPositionX == '3px') || (cssStyleDeclaration['background-position-x'] == '3px'); + var ySupport = (cssStyleDeclaration.backgroundPositionY == '5px') || (cssStyleDeclaration['background-position-y'] == '5px'); + return xSupport && ySupport; + }); +}); \ No newline at end of file diff --git a/feature-detects/css-backgroundsizecover.js b/feature-detects/css-backgroundsizecover.js index 3815538ed0..fd14125944 100644 --- a/feature-detects/css-backgroundsizecover.js +++ b/feature-detects/css-backgroundsizecover.js @@ -1,11 +1,9 @@ // developer.mozilla.org/en/CSS/background-size -Modernizr.testStyles(' #modernizr { background-size: cover; } ', function(elem, rule){ - - var bool = (window.getComputedStyle ? - getComputedStyle(elem, null) : - elem.currentStyle)['background-size'] == 'cover'; - - Modernizr.addTest('bgsizecover', bool); +Modernizr.testStyles( '#modernizr{background-size:cover}', function( elem ) { + var style = window.getComputedStyle + ? window.getComputedStyle( elem, null ) + : elem.currentStyle; + Modernizr.addTest( 'bgsizecover', style.backgroundSize == 'cover' ); }); \ No newline at end of file diff --git a/feature-detects/css-calc.js b/feature-detects/css-calc.js new file mode 100644 index 0000000000..42b5d57b3e --- /dev/null +++ b/feature-detects/css-calc.js @@ -0,0 +1,10 @@ +// Method of allowing calculated values for length units, i.e. width: calc(100%-3em) http://caniuse.com/#search=calc +// By @calvein + +Modernizr.addTest('csscalc', function(el, prop, value) { + prop = 'width:'; + value = 'calc(10px);'; + el = document.createElement('div'); + el.style.cssText = prop + Modernizr._prefixes.join(value + prop); + return !!el.style.length; +}); \ No newline at end of file diff --git a/feature-detects/css-cubicbezierrange.js b/feature-detects/css-cubicbezierrange.js index c3ccb0ea89..28e72aab8c 100644 --- a/feature-detects/css-cubicbezierrange.js +++ b/feature-detects/css-cubicbezierrange.js @@ -2,7 +2,7 @@ // By @calvein Modernizr.addTest('cubicbezierrange', function() { - el = document.createElement('div'); + var el = document.createElement('div'); el.style.cssText = Modernizr._prefixes.join('transition-timing-function' + ':cubic-bezier(1,0,0,1.1); '); return !!el.style.length; }); diff --git a/feature-detects/css-hyphens.js b/feature-detects/css-hyphens.js index a37417658a..8215dd5ecf 100644 --- a/feature-detects/css-hyphens.js +++ b/feature-detects/css-hyphens.js @@ -1,17 +1,19 @@ -/* see dev.davidnewton.ca/the-current-state-of-hyphenation-on-the-web - dev.davidnewton.ca/demos/hyphenation/test.html +/* see http://davidnewton.ca/the-current-state-of-hyphenation-on-the-web + http://davidnewton.ca/demos/hyphenation/test.html There are three tests: - 1. csshyphens - tests hyphens:auto actually adds hyphens to text - 2. softhyphens - tests that ­ does its job - 3. softhyphensfind - tests that in-browser Find functionality still works correctly with ­ + 1. csshyphens - tests hyphens:auto actually adds hyphens to text + 2. softhyphens - tests that ­ does its job + 3. softhyphensfind - tests that in-browser Find functionality still works correctly with ­ -These tests currently require document.body to be present +These tests currently require document.body to be present -Hyphenation is language specific, sometimes. +Hyphenation is language specific, sometimes. See for more details: http://code.google.com/p/hyphenator/source/diff?spec=svn975&r=975&format=side&path=/trunk/Hyphenator.js#sc_svn975_313 +If loading Hyphenator.js via Modernizr.load, be cautious of issue 158: http://code.google.com/p/hyphenator/issues/detail?id=158 + More details at https://github.com/Modernizr/Modernizr/issues/312 */ @@ -19,7 +21,7 @@ More details at https://github.com/Modernizr/Modernizr/issues/312 (function() { if (!document.body){ - window.console && console.warn('document.body doesn\'t exist. Modernizr hyphens test needs it.') + window.console && console.warn('document.body doesn\'t exist. Modernizr hyphens test needs it.'); return; } @@ -34,15 +36,13 @@ More details at https://github.com/Modernizr/Modernizr/issues/312 spanHeight = 0, spanWidth = 0, result = false, - result1 = false, - result2 = false, firstChild = document.body.firstElementChild || document.body.firstChild; - + div.appendChild(span); span.innerHTML = 'Bacon ipsum dolor sit amet jerky velit in culpa hamburger et. Laborum dolor proident, enim dolore duis commodo et strip steak. Salami anim et, veniam consectetur dolore qui tenderloin jowl velit sirloin. Et ad culpa, fatback cillum jowl ball tip ham hock nulla short ribs pariatur aute. Pig pancetta ham bresaola, ut boudin nostrud commodo flank esse cow tongue culpa. Pork belly bresaola enim pig, ea consectetur nisi. Fugiat officia turkey, ea cow jowl pariatur ullamco proident do laborum velit sausage. Magna biltong sint tri-tip commodo sed bacon, esse proident aliquip. Ullamco ham sint fugiat, velit in enim sed mollit nulla cow ut adipisicing nostrud consectetur. Proident dolore beef ribs, laborum nostrud meatball ea laboris rump cupidatat labore culpa. Shankle minim beef, velit sint cupidatat fugiat tenderloin pig et ball tip. Ut cow fatback salami, bacon ball tip et in shank strip steak bresaola. In ut pork belly sed mollit tri-tip magna culpa veniam, short ribs qui in andouille ham consequat. Dolore bacon t-bone, velit short ribs enim strip steak nulla. Voluptate labore ut, biltong swine irure jerky. Cupidatat excepteur aliquip salami dolore. Ball tip strip steak in pork dolor. Ad in esse biltong. Dolore tenderloin exercitation ad pork loin t-bone, dolore in chicken ball tip qui pig. Ut culpa tongue, sint ribeye dolore ex shank voluptate hamburger. Jowl et tempor, boudin pork chop labore ham hock drumstick consectetur tri-tip elit swine meatball chicken ground round. Proident shankle mollit dolore. Shoulder ut duis t-bone quis reprehenderit. Meatloaf dolore minim strip steak, laboris ea aute bacon beef ribs elit shank in veniam drumstick qui. Ex laboris meatball cow tongue pork belly. Ea ball tip reprehenderit pig, sed fatback boudin dolore flank aliquip laboris eu quis. Beef ribs duis beef, cow corned beef adipisicing commodo nisi deserunt exercitation. Cillum dolor t-bone spare ribs, ham hock est sirloin. Brisket irure meatloaf in, boudin pork belly sirloin ball tip. Sirloin sint irure nisi nostrud aliqua. Nostrud nulla aute, enim officia culpa ham hock. Aliqua reprehenderit dolore sunt nostrud sausage, ea boudin pork loin ut t-bone ham tempor. Tri-tip et pancetta drumstick laborum. Ham hock magna do nostrud in proident. Ex ground round fatback, venison non ribeye in.'; document.body.insertBefore(div, firstChild); - + /* get size of unhyphenated text */ divStyle.cssText = 'position:absolute;top:0;left:0;width:5em;text-align:justify;text-justification:newspaper;'; spanHeight = span.offsetHeight; @@ -50,8 +50,8 @@ More details at https://github.com/Modernizr/Modernizr/issues/312 /* compare size with hyphenated text */ divStyle.cssText = 'position:absolute;top:0;left:0;width:5em;text-align:justify;'+ - 'text-justification:newspaper;' - + Modernizr._prefixes.join('hyphens:auto; '); + 'text-justification:newspaper;'+ + Modernizr._prefixes.join('hyphens:auto; '); result = (span.offsetHeight != spanHeight || span.offsetWidth != spanWidth); @@ -82,8 +82,8 @@ More details at https://github.com/Modernizr/Modernizr/issues/312 divStyle.cssText = 'position:absolute;top:0;left:0;overflow:visible;width:1.25em;'; div.appendChild(span); document.body.insertBefore(div, firstChild); - - + + /* get height of unwrapped text */ span.innerHTML = 'mm'; spanSize = span.offsetHeight; @@ -110,7 +110,7 @@ More details at https://github.com/Modernizr/Modernizr/issues/312 if (result1 === true && result2 === true) { result = true; } document.body.removeChild(div); div.removeChild(span); - + return result; } catch(e) { return false; @@ -134,9 +134,9 @@ More details at https://github.com/Modernizr/Modernizr/issues/312 div.innerHTML = testword + delimiter + testword; document.body.insertBefore(div, firstChild); - document.body.insertBefore(dummy, firstChild); + document.body.insertBefore(dummy, div); + - /* reset the selection to the dummy input element, i.e. BEFORE the div container * stackoverflow.com/questions/499126/jquery-set-cursor-position-in-text-area */ if (dummy.setSelectionRange) { @@ -155,7 +155,7 @@ More details at https://github.com/Modernizr/Modernizr/issues/312 result = window.find(testword + testword); } else { try { - textrange = self.document.body.createTextRange(); + textrange = window.self.document.body.createTextRange(); result = textrange.findText(testword + testword); } catch(e) { result = false; @@ -188,7 +188,7 @@ More details at https://github.com/Modernizr/Modernizr/issues/312 Modernizr.addTest("softhyphens", function() { try { // use numeric entity instead of ­ in case it's XHTML - return test_hyphens('­', true) && test_hyphens('​', false); + return test_hyphens('­', true) && test_hyphens('​', false); } catch(e) { return false; } diff --git a/feature-detects/css-lastchild.js b/feature-detects/css-lastchild.js new file mode 100644 index 0000000000..058c342f73 --- /dev/null +++ b/feature-detects/css-lastchild.js @@ -0,0 +1,7 @@ +// last-child pseudo selector +// https://github.com/Modernizr/Modernizr/pull/304 + + +Modernizr.testStyles("#modernizr div {width:100px} #modernizr :last-child{width:200px;display:block}", function(elem) { + return elem.lastChild.offsetWidth > elem.firstChild.offsetWidth; +}, 1); diff --git a/feature-detects/css-mask.js b/feature-detects/css-mask.js index a6868f1941..1afeab4e7b 100644 --- a/feature-detects/css-mask.js +++ b/feature-detects/css-mask.js @@ -1,10 +1,13 @@ +// this tests passes for webkit's proprietary `-webkit-mask` feature +// www.webkit.org/blog/181/css-masks/ +// developer.apple.com/library/safari/#documentation/InternetWeb/Conceptual/SafariVisualEffectsProgGuide/Masks/Masks.html -// Non-standard feature available in webkit only, so far. +// it does not pass mozilla's implementation of `mask` for SVG -// http://www.webkit.org/blog/181/css-masks/ -// http://developer.apple.com/library/safari/#documentation/InternetWeb/Conceptual/SafariVisualEffectsProgGuide/Masks/Masks.html +// developer.mozilla.org/en/CSS/mask +// developer.mozilla.org/En/Applying_SVG_effects_to_HTML_content // Can combine with clippaths for awesomeness: http://generic.cx/for/webkit/test.html -Modernizr.addTest('cssmask', Modernizr.testAllProps('mask')); +Modernizr.addTest('cssmask', Modernizr.testAllProps('mask-repeat')); diff --git a/feature-detects/css-pointerevents.js b/feature-detects/css-pointerevents.js index 52abfcab94..f18a5c1a7f 100644 --- a/feature-detects/css-pointerevents.js +++ b/feature-detects/css-pointerevents.js @@ -1,5 +1,12 @@ + // developer.mozilla.org/en/CSS/pointer-events -// github.com/ausi/Feature-detection-technique-for-pointer-events + +// Test and project pages: +// ausi.github.com/Feature-detection-technique-for-pointer-events/ +// github.com/ausi/Feature-detection-technique-for-pointer-events/wiki +// github.com/Modernizr/Modernizr/issues/80 + + Modernizr.addTest('pointerevents', function(){ var element = document.createElement('x'), documentElement = document.documentElement, @@ -11,8 +18,8 @@ Modernizr.addTest('pointerevents', function(){ element.style.pointerEvents = 'auto'; element.style.pointerEvents = 'x'; documentElement.appendChild(element); - supports = getComputedStyle && + supports = getComputedStyle && getComputedStyle(element, '').pointerEvents === 'auto'; documentElement.removeChild(element); return !!supports; -}); \ No newline at end of file +}); diff --git a/feature-detects/css-regions.js b/feature-detects/css-regions.js new file mode 100644 index 0000000000..c8995c3290 --- /dev/null +++ b/feature-detects/css-regions.js @@ -0,0 +1,55 @@ +// CSS Regions +// http://www.w3.org/TR/css3-regions/ +// By: Mihai Balan + +// We start with a CSS parser test then we check page geometry to see if it's affected by regions +// Later we might be able to retire the second part, as WebKit builds with the false positives die out + +Modernizr.addTest('regions', function() { + + /* Get the 'flowFrom' property name available in the browser. Either default or vendor prefixed. + If the property name can't be found we'll get Boolean 'false' and fail quickly */ + var flowFromProperty = Modernizr.prefixed("flowFrom"), + flowIntoProperty = Modernizr.prefixed("flowInto"); + + if (!flowFromProperty || !flowIntoProperty){ + return false; + } + + /* If CSS parsing is there, try to determine if regions actually work. */ + var container = document.createElement('div'), + content = document.createElement('div'), + region = document.createElement('div'), + + /* we create a random, unlikely to be generated flow number to make sure we don't + clash with anything more vanilla, like 'flow', or 'article', or 'f1' */ + flowName = 'modernizr_flow_for_regions_check'; + + /* First create a div with two adjacent divs inside it. The first will be the + content, the second will be the region. To be able to distinguish between the two, + we'll give the region a particular padding */ + content.innerText = 'M'; + container.style.cssText = 'top: 150px; left: 150px; padding: 0px;'; + region.style.cssText = 'width: 50px; height: 50px; padding: 42px;'; + + region.style[flowFromProperty] = flowName; + container.appendChild(content); + container.appendChild(region); + document.documentElement.appendChild(container); + + /* Now compute the bounding client rect, before and after attempting to flow the + content div in the region div. If regions are enabled, the after bounding rect + should reflect the padding of the region div.*/ + var flowedRect, delta, + plainRect = content.getBoundingClientRect(); + + + content.style[flowIntoProperty] = flowName; + flowedRect = content.getBoundingClientRect(); + + delta = flowedRect.left - plainRect.left; + document.documentElement.removeChild(container); + content = region = container = undefined; + + return (delta == 42); +}); diff --git a/feature-detects/css-remunit.js b/feature-detects/css-remunit.js index 62135dca59..38e15b82b3 100644 --- a/feature-detects/css-remunit.js +++ b/feature-detects/css-remunit.js @@ -1,7 +1,7 @@ // test by github.com/nsfmc -// "The 'rem' unit ('root em') is relative to the computed +// "The 'rem' unit ('root em') is relative to the computed // value of the 'font-size' value of the root element." // http://www.w3.org/TR/css3-values/#relative0 // you can test by checking if the prop was ditched @@ -14,6 +14,6 @@ Modernizr.addTest('cssremunit', function(){ try { div.style.fontSize = '3rem'; } catch(er){} - return /rem/.test(div.style.fontSize) + return (/rem/).test(div.style.fontSize); }); diff --git a/feature-detects/css-scrollbars.js b/feature-detects/css-scrollbars.js index 8ddd6ab80d..f26de9fdb8 100644 --- a/feature-detects/css-scrollbars.js +++ b/feature-detects/css-scrollbars.js @@ -1,19 +1,19 @@ // Stylable scrollbars detection Modernizr.addTest('cssscrollbar', function() { - + var bool, - - styles = "#modernizr{overflow: scroll; width: 40px }#" + - Modernizr._prefixes + + styles = "#modernizr{overflow: scroll; width: 40px }#" + + Modernizr._prefixes .join("scrollbar{width:0px}"+' #modernizr::') .split('#') .slice(1) .join('#') + "scrollbar{width:0px}"; - + Modernizr.testStyles(styles, function(node) { bool = 'scrollWidth' in node && node.scrollWidth == 40; }); - + return bool; }); diff --git a/feature-detects/css-subpixelfont.js b/feature-detects/css-subpixelfont.js new file mode 100644 index 0000000000..15b51d0126 --- /dev/null +++ b/feature-detects/css-subpixelfont.js @@ -0,0 +1,23 @@ +/* + * Test for SubPixel Font Rendering + * (to infer if GDI or DirectWrite is used on Windows) + * Authors: @derSchepp, @gerritvanaaken, @rodneyrehm, @yatil, @ryanseddon + * Web: https://github.com/gerritvanaaken/subpixeldetect + */ +Modernizr.addTest('subpixelfont', function() { + var bool, + styles = "#modernizr{position: absolute; top: -10em; visibility:hidden; font: normal 10px arial;}#subpixel{float: left; font-size: 33.3333%;}"; + + // see https://github.com/Modernizr/Modernizr/blob/master/modernizr.js#L97 + Modernizr.testStyles(styles, function(elem) { + var subpixel = elem.firstChild; + + subpixel.innerHTML = 'This is a text written in Arial'; + + bool = window.getComputedStyle + ? window.getComputedStyle(subpixel, null).getPropertyValue("width") !== '44px' + : false; + }, 1, ['subpixel']); + + return bool; +}); diff --git a/feature-detects/dart.js b/feature-detects/dart.js new file mode 100644 index 0000000000..9a8bc823b1 --- /dev/null +++ b/feature-detects/dart.js @@ -0,0 +1,6 @@ +// Dart +// By Theodoor van Donge + +// https://chromiumcodereview.appspot.com/9232049/ + +Modernizr.addTest('dart', !!Modernizr.prefixed('startDart', navigator)); diff --git a/feature-detects/elem-progress-meter.js b/feature-detects/elem-progress-meter.js index b019b46ae2..cfa4bdf401 100644 --- a/feature-detects/elem-progress-meter.js +++ b/feature-detects/elem-progress-meter.js @@ -2,10 +2,10 @@ //tests for progressbar-support. All browsers that don't support progressbar returns undefined =) Modernizr.addTest("progressbar",function(){ - return document.createElement('progress').max != undefined; + return document.createElement('progress').max !== undefined; }); //tests for meter-support. All browsers that don't support meters returns undefined =) Modernizr.addTest("meter",function(){ - return document.createElement('meter').max != undefined; + return document.createElement('meter').max !== undefined; }); diff --git a/feature-detects/elem-ruby.js b/feature-detects/elem-ruby.js index e0cfb9cba7..dbb978fab2 100644 --- a/feature-detects/elem-ruby.js +++ b/feature-detects/elem-ruby.js @@ -1,56 +1,53 @@ -/* - * Browser support test for the HTML5 , and elements - * - * by @alrra - * - */ - -Modernizr.addTest('ruby', function() { - - var ruby = document.createElement('ruby'), - rt = document.createElement('rt'), - rp = document.createElement('rp'), - docElement = document.documentElement, - displayStyleProperty = 'display', - fontSizeStyleProperty = 'fontSize'; // 'fontSize' - because it`s only used for IE6 and IE7 - - ruby.appendChild(rp); - ruby.appendChild(rt); - docElement.appendChild(ruby); - - // browsers that support hide the via "display:none" - if((getStyle(rp,displayStyleProperty) == 'none') // for non-IE browsers - // but in IE browsers has "display:inline" so, the test needs other conditions: - || (getStyle(ruby,displayStyleProperty) == 'ruby' && getStyle(rt,displayStyleProperty) == 'ruby-text') // for IE8 & IE9 - || (getStyle(rp,fontSizeStyleProperty) == '6pt' && getStyle(rt,fontSizeStyleProperty) == '6pt')) { // for IE6 & IE7 - - cleanUp(); - return true; - - } else { - - cleanUp(); - return false; - - } - - function getStyle(element, styleProperty) { - var result; - - if (window.getComputedStyle) { // for non-IE browsers - result = document.defaultView.getComputedStyle(element,null).getPropertyValue(styleProperty); - } else if (element.currentStyle) { // for IE - result = element.currentStyle[styleProperty]; - } - - return result; - } - - function cleanUp() { - docElement.removeChild(ruby); // the removed child node still exists in memory so ... - ruby = null; - rt = null; - rp = null; - } +// Browser support test for the HTML5 , and elements +// http://www.whatwg.org/specs/web-apps/current-work/multipage/text-level-semantics.html#the-ruby-element +// +// by @alrra + +Modernizr.addTest('ruby', function () { + + var ruby = document.createElement('ruby'), + rt = document.createElement('rt'), + rp = document.createElement('rp'), + docElement = document.documentElement, + displayStyleProperty = 'display', + fontSizeStyleProperty = 'fontSize'; // 'fontSize' - because it`s only used for IE6 and IE7 + + ruby.appendChild(rp); + ruby.appendChild(rt); + docElement.appendChild(ruby); + + // browsers that support hide the via "display:none" + if ( getStyle(rp, displayStyleProperty) == 'none' || // for non-IE browsers + // but in IE browsers has "display:inline" so, the test needs other conditions: + getStyle(ruby, displayStyleProperty) == 'ruby' && getStyle(rt, displayStyleProperty) == 'ruby-text' || // for IE8 & IE9 + getStyle(rp, fontSizeStyleProperty) == '6pt' && getStyle(rt, fontSizeStyleProperty) == '6pt' ) { // for IE6 & IE7 + + cleanUp(); + return true; + + } else { + cleanUp(); + return false; + } + + function getStyle( element, styleProperty ) { + var result; + + if ( window.getComputedStyle ) { // for non-IE browsers + result = document.defaultView.getComputedStyle(element,null).getPropertyValue(styleProperty); + } else if ( element.currentStyle ) { // for IE + result = element.currentStyle[styleProperty]; + } + + return result; + } + + function cleanUp() { + docElement.removeChild(ruby); + // the removed child node still exists in memory, so ... + ruby = null; + rt = null; + rp = null; + } }); diff --git a/feature-detects/emoji.js b/feature-detects/emoji.js index dbb2a90661..271db65944 100644 --- a/feature-detects/emoji.js +++ b/feature-detects/emoji.js @@ -7,5 +7,5 @@ Modernizr.addTest('emoji', function() { ctx.textBaseline = 'top'; ctx.font = '32px Arial'; ctx.fillText('\ud83d\ude03', 0, 0); // "smiling face with open mouth" emoji - return ctx.getImageData(16, 16, 1, 1).data[0] != 0; + return ctx.getImageData(16, 16, 1, 1).data[0] !== 0; }); \ No newline at end of file diff --git a/feature-detects/event-deviceorientation-motion.js b/feature-detects/event-deviceorientation-motion.js index 057135cd48..644e671ef9 100644 --- a/feature-detects/event-deviceorientation-motion.js +++ b/feature-detects/event-deviceorientation-motion.js @@ -1,5 +1,5 @@ -//By Shi Chuan -//Part of Device Access aspect of HTML5, same category as geolocation +//By Shi Chuan +//Part of Device Access aspect of HTML5, same category as geolocation //W3C Editor's Draft at http://dev.w3.org/geo/api/spec-source-orientation.html //Implementation by iOS Safari at http://goo.gl/fhce3 and http://goo.gl/rLKz8 diff --git a/feature-detects/file-filesystem.js b/feature-detects/file-filesystem.js index 4827825a13..ced6761513 100644 --- a/feature-detects/file-filesystem.js +++ b/feature-detects/file-filesystem.js @@ -1,19 +1,9 @@ - -// filesystem API - +// Filesystem API // dev.w3.org/2009/dap/file-system/file-dir-sys.html // The API will be present in Chrome incognito, but will throw an exception. -// code.google.com/p/chromium/issues/detail?id=93417 - - -Modernizr.addTest('filesystem', function(){ - - var prefixes = Modernizr._domPrefixes; - - for ( var i = -1, len = prefixes.length; ++i < len; ){ - if ( window[prefixes[i] + 'RequestFileSystem'] ) return true; - } - return 'requestFileSystem' in window; +// See crbug.com/93417 +// +// By Eric Bidelman (@ebidel) -}); +Modernizr.addTest('filesystem', !!Modernizr.prefixed('requestFileSystem', window)); \ No newline at end of file diff --git a/feature-detects/forms-validation.js b/feature-detects/forms-validation.js index f517460e3d..5e5cfa6e54 100644 --- a/feature-detects/forms-validation.js +++ b/feature-detects/forms-validation.js @@ -1,12 +1,83 @@ +// This implementation only tests support for interactive form validation. +// To check validation for a specific type or a specific other constraint, +// the test can be combined: +// - Modernizr.inputtypes.numer && Modernizr.formvalidation (browser supports rangeOverflow, typeMismatch etc. for type=number) +// - Modernizr.input.required && Modernizr.formvalidation (browser supports valueMissing) +// +(function(document, Modernizr){ -// interactive form validation -// www.whatwg.org/specs/web-apps/current-work/multipage/association-of-controls-and-forms.html#interactively-validate-the-constraints -// This easy test can false positive in Safari 5.0.5 which had checkValidity, but -// it did not block forms from submitting. -// For a more comprehensive detect to handle that case, please see: -// github.com/Modernizr/Modernizr/pull/315 +Modernizr.formvalidationapi = false; +Modernizr.formvalidationmessage = false; Modernizr.addTest('formvalidation', function(){ - return typeof document.createElement('form').checkValidity == 'function'; + var form = document.createElement('form'); + if ( !('checkValidity' in form) ) { + return false; + } + var body = document.body, + + html = document.documentElement, + + bodyFaked = false, + + invaildFired = false, + + input; + + Modernizr.formvalidationapi = true; + + // Prevent form from being submitted + form.onsubmit = function(e) { + //Opera does not validate form, if submit is prevented + if ( !window.opera ) { + e.preventDefault(); + } + e.stopPropagation(); + }; + + // Calling form.submit() doesn't trigger interactive validation, + // use a submit button instead + //older opera browsers need a name attribute + form.innerHTML = ''; + + // FF4 doesn't trigger "invalid" event if form is not in the DOM tree + // Chrome throws error if invalid input is not visible when submitting + form.style.position = 'absolute'; + form.style.top = '-99999em'; + + // We might in in which case we need to create body manually + if ( !body ) { + bodyFaked = true; + body = document.createElement('body'); + //avoid crashing IE8, if background image is used + body.style.background = ""; + html.appendChild(body); + } + + body.appendChild(form); + + input = form.getElementsByTagName('input')[0]; + + // Record whether "invalid" event is fired + input.oninvalid = function(e) { + invaildFired = true; + e.preventDefault(); + e.stopPropagation(); + }; + + //Opera does not fully support the validationMessage property + Modernizr.formvalidationmessage = !!input.validationMessage; + + // Submit form by clicking submit button + form.getElementsByTagName('button')[0].click(); + + // Don't forget to clean up + body.removeChild(form); + bodyFaked && html.removeChild(body); + + return invaildFired; }); + + +})(document, window.Modernizr); \ No newline at end of file diff --git a/feature-detects/gamepad.js b/feature-detects/gamepad.js index 50aaa02f6a..da636a94c1 100644 --- a/feature-detects/gamepad.js +++ b/feature-detects/gamepad.js @@ -1,6 +1,5 @@ // GamePad API -// https://wiki.mozilla.org/GamepadAPI -// Note: this feature detection test has been confirmed with the developers -// of the GamePad API implementation in FF -// By Addy Osmani -Modernizr.addTest('gamepad', 'gamepads' in navigator); +// https://dvcs.w3.org/hg/gamepad/raw-file/default/gamepad.html +// By Eric Bidelman + +Modernizr.addTest('gamepads', !!Modernizr.prefixed('gamepads', navigator)); diff --git a/feature-detects/getusermedia.js b/feature-detects/getusermedia.js index 6d43d88847..4c38bec9f9 100644 --- a/feature-detects/getusermedia.js +++ b/feature-detects/getusermedia.js @@ -1,4 +1,5 @@ // getUserMedia // http://www.whatwg.org/specs/web-apps/current-work/multipage/video-conferencing-and-peer-to-peer-communication.html -// By Addy Osmani -Modernizr.addTest('getusermedia', !!('getUserMedia' in navigator)); \ No newline at end of file +// By Eric Bidelman + +Modernizr.addTest('getusermedia', !!Modernizr.prefixed('getUserMedia', navigator)); \ No newline at end of file diff --git a/feature-detects/network-connection.js b/feature-detects/network-connection.js index 7f63fa5a6e..242cdfe829 100644 --- a/feature-detects/network-connection.js +++ b/feature-detects/network-connection.js @@ -13,10 +13,10 @@ // for more rigorous network testing, consider boomerang.js: github.com/bluesmoon/boomerang/ Modernizr.addTest('lowbandwidth', function() { - + var connection = navigator.connection || { type: 0 }; // polyfill - return connection.type == 3 // connection.CELL_2G - || connection.type == 4 // connection.CELL_3G - || /^[23]g$/.test(connection.type); // string value in new spec + return connection.type == 3 || // connection.CELL_2G + connection.type == 4 || // connection.CELL_3G + /^[23]g$/.test(connection.type); // string value in new spec }); diff --git a/feature-detects/network-xhr2.js b/feature-detects/network-xhr2.js new file mode 100644 index 0000000000..8d3fb51694 --- /dev/null +++ b/feature-detects/network-xhr2.js @@ -0,0 +1,13 @@ + + +// XML HTTP Request Level 2 +// www.w3.org/TR/XMLHttpRequest2/ + +// Much more details at github.com/Modernizr/Modernizr/issues/385 + +// all three of these details report consistently across all target browsers: +// !!(window.ProgressEvent); +// !!(window.FormData); +// window.XMLHttpRequest && "withCredentials" in new XMLHttpRequest; + +Modernizr.addTest('xhr2', 'FormData' in window); diff --git a/feature-detects/quota-management-api.js b/feature-detects/quota-management-api.js index 9a909850f4..5ce2b02d00 100644 --- a/feature-detects/quota-management-api.js +++ b/feature-detects/quota-management-api.js @@ -1,9 +1,8 @@ - -// Quota storage management API +// Quota Storage Management API // This API can be used to check how much quota an origin is using and request more -// Currently only implemented in WebKit -// https://groups.google.com/a/chromium.org/group/chromium-html5/msg/5261d24266ba4366 +// Currently only implemented in Chrome. +// https://developers.google.com/chrome/whitepapers/storage // By Addy Osmani Modernizr.addTest('quotamanagement', function(){ diff --git a/feature-detects/style-scoped.js b/feature-detects/style-scoped.js new file mode 100644 index 0000000000..3add1b07e3 --- /dev/null +++ b/feature-detects/style-scoped.js @@ -0,0 +1,6 @@ +// Browser support test for '].join(''); + style = ['­',''].join(''); div.id = mod; // IE6 will false positive on some tests due to the style element inside the test div somehow interfering offsetHeight, so insert it into body or fakebody. // Opera will act all quirky when injecting elements in documentElement when page is served as xml, needs fakebody too. #270 - fakeBody.innerHTML += style; + (body ? div : fakeBody).innerHTML += style; fakeBody.appendChild(div); - if(!body){ + if ( !body ) { //avoid crashing IE8, if background image is used fakeBody.style.background = ""; docElement.appendChild(fakeBody); @@ -125,8 +137,9 @@ window.Modernizr = (function( window, document, undefined ) { return !!ret; }, + /*>>teststyles*/ - + /*>>mq*/ // adapted from matchMedia polyfill // by Scott Jehl and Paul Irish // gist.github.com/786768 @@ -148,12 +161,18 @@ window.Modernizr = (function( window, document, undefined ) { return bool; }, + /*>>mq*/ - /** - * isEventSupported determines if a given element supports the given event - * function from yura.thinkweb2.com/isEventSupported/ - */ + /*>>hasevent*/ + // + // isEventSupported determines if a given element supports the given event + // kangax.github.com/iseventsupported/ + // + // The following results are known incorrects: + // Modernizr.hasEvent("webkitTransitionEnd", elem) // false negative + // Modernizr.hasEvent("textInput") // in Webkit. github.com/Modernizr/Modernizr/issues/333 + // ... isEventSupported = (function() { var TAGNAMES = { @@ -191,43 +210,45 @@ window.Modernizr = (function( window, document, undefined ) { return isSupported; } return isEventSupported; - })(); + })(), + /*>>hasevent*/ + + // TODO :: Add flag for hasownprop ? didn't last time // hasOwnProperty shim by kangax needed for Safari 2.0 support - var _hasOwnProperty = ({}).hasOwnProperty, hasOwnProperty; + _hasOwnProperty = ({}).hasOwnProperty, hasOwnProp; + if ( !is(_hasOwnProperty, 'undefined') && !is(_hasOwnProperty.call, 'undefined') ) { - hasOwnProperty = function (object, property) { + hasOwnProp = function (object, property) { return _hasOwnProperty.call(object, property); }; } else { - hasOwnProperty = function (object, property) { /* yes, this can give false positives/negatives, but most of the time we don't care about those */ + hasOwnProp = function (object, property) { /* yes, this can give false positives/negatives, but most of the time we don't care about those */ return ((property in object) && is(object.constructor.prototype[property], 'undefined')); }; } - // Taken from ES5-shim https://github.com/kriskowal/es5-shim/blob/master/es5-shim.js - // ES-5 15.3.4.5 - // http://es5.github.com/#x15.3.4.5 + // Adapted from ES5-shim https://github.com/kriskowal/es5-shim/blob/master/es5-shim.js + // es5.github.com/#x15.3.4.5 if (!Function.prototype.bind) { - Function.prototype.bind = function bind(that) { - + var target = this; - + if (typeof target != "function") { throw new TypeError(); } - + var args = slice.call(arguments, 1), bound = function () { if (this instanceof bound) { - + var F = function(){}; F.prototype = target.prototype; - var self = new F; + var self = new F(); var result = target.apply( self, @@ -239,7 +260,7 @@ window.Modernizr = (function( window, document, undefined ) { return self; } else { - + return target.apply( that, args.concat(slice.call(arguments)) @@ -248,7 +269,7 @@ window.Modernizr = (function( window, document, undefined ) { } }; - + return bound; }; } @@ -281,20 +302,38 @@ window.Modernizr = (function( window, document, undefined ) { return !!~('' + str).indexOf(substr); } - /** - * testProps 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. - */ + /*>>testprop*/ + + // testProps is a generic CSS / DOM property test. + + // In testing support for a given CSS property, it's legit to test: + // `elem.style[styleName] !== undefined` + // If the property is supported it will return an empty string, + // if unsupported it will return undefined. + + // We'll take advantage of this quick test and skip setting a style + // on our modernizr element, but instead just testing undefined vs + // empty string. + + // Because the testing of the CSS property names (with "-", as + // opposed to the camelCase DOM properties) is non-portable and + // non-standard but works in WebKit and IE (but not Gecko or Opera), + // we explicitly reject properties with dashes so that authors + // developing in WebKit or IE first don't end up with + // browser-specific content by accident. + function testProps( props, prefixed ) { for ( var i in props ) { - if ( mStyle[ props[i] ] !== undefined ) { - return prefixed == 'pfx' ? props[i] : true; + var prop = props[i]; + if ( !contains(prop, "-") && mStyle[prop] !== undefined ) { + return prefixed == 'pfx' ? prop : true; } } return false; } + /*>>testprop*/ + // TODO :: add testDOMProps /** * testDOMProps is a generic DOM property test; if a browser supports * a certain property, it won't return undefined for it. @@ -312,7 +351,7 @@ window.Modernizr = (function( window, document, undefined ) { // default to autobind unless override return item.bind(elem || obj); } - + // return the unbound function or obj or value return item; } @@ -320,6 +359,7 @@ window.Modernizr = (function( window, document, undefined ) { return false; } + /*>>testallprops*/ /** * testPropsAll tests a list of DOM properties we want to check against. * We specify literally ALL possible (known and/or likely) properties on @@ -328,7 +368,7 @@ window.Modernizr = (function( window, document, undefined ) { */ function testPropsAll( prop, prefixed, elem ) { - var ucProp = prop.charAt(0).toUpperCase() + prop.substr(1), + var ucProp = prop.charAt(0).toUpperCase() + prop.slice(1), props = (prop + ' ' + cssomPrefixes.join(ucProp + ' ') + ucProp).split(' '); // did they call .prefixed('boxSizing') or are we just testing a prop? @@ -341,54 +381,7 @@ window.Modernizr = (function( window, document, undefined ) { return testDOMProps(props, prefixed, elem); } } - - /** - * testBundle tests a list of CSS features that require element and style injection. - * By bundling them together we can reduce the need to touch the DOM multiple times. - */ - /*>>testBundle*/ - var testBundle = (function( styles, tests ) { - var style = styles.join(''), - len = tests.length; - - injectElementWithStyles(style, function( node, rule ) { - var style = document.styleSheets[document.styleSheets.length - 1], - // IE8 will bork if you create a custom build that excludes both fontface and generatedcontent tests. - // So we check for cssRules and that there is a rule available - // More here: github.com/Modernizr/Modernizr/issues/288 & github.com/Modernizr/Modernizr/issues/293 - cssText = style ? (style.cssRules && style.cssRules[0] ? style.cssRules[0].cssText : style.cssText || '') : '', - children = node.childNodes, hash = {}; - - while ( len-- ) { - hash[children[len].id] = children[len]; - } - - /*>>touch*/ Modernizr['touch'] = ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch || (hash['touch'] && hash['touch'].offsetTop) === 9; /*>>touch*/ - /*>>csstransforms3d*/ Modernizr['csstransforms3d'] = (hash['csstransforms3d'] && hash['csstransforms3d'].offsetLeft) === 9 && hash['csstransforms3d'].offsetHeight === 3; /*>>csstransforms3d*/ - /*>>generatedcontent*/Modernizr['generatedcontent'] = (hash['generatedcontent'] && hash['generatedcontent'].offsetHeight) >= 1; /*>>generatedcontent*/ - /*>>fontface*/ Modernizr['fontface'] = /src/i.test(cssText) && - cssText.indexOf(rule.split(' ')[0]) === 0; /*>>fontface*/ - }, len, tests); - - })([ - // Pass in styles to be injected into document - /*>>fontface*/ '@font-face {font-family:"font";src:url("https://")}' /*>>fontface*/ - - /*>>touch*/ ,['@media (',prefixes.join('touch-enabled),('),mod,')', - '{#touch{top:9px;position:absolute}}'].join('') /*>>touch*/ - - /*>>csstransforms3d*/ ,['@media (',prefixes.join('transform-3d),('),mod,')', - '{#csstransforms3d{left:9px;position:absolute;height:3px;}}'].join('')/*>>csstransforms3d*/ - - /*>>generatedcontent*/,['#generatedcontent:after{content:"',smile,'";visibility:hidden}'].join('') /*>>generatedcontent*/ - ], - [ - /*>>fontface*/ 'fontface' /*>>fontface*/ - /*>>touch*/ ,'touch' /*>>touch*/ - /*>>csstransforms3d*/ ,'csstransforms3d' /*>>csstransforms3d*/ - /*>>generatedcontent*/,'generatedcontent' /*>>generatedcontent*/ - - ]);/*>>testBundle*/ + /*>>testallprops*/ /** @@ -400,13 +393,13 @@ window.Modernizr = (function( window, document, undefined ) { // dev.w3.org/csswg/css3-flexbox tests['flexbox'] = function() { - return testPropsAll('flexOrder'); + return testPropsAll('flexWrap'); }; // The *old* flexbox // www.w3.org/TR/2009/WD-css3-flexbox-20090723/ - tests['flexbox-legacy'] = function() { + tests['flexboxlegacy'] = function() { return testPropsAll('boxDirection'); }; @@ -423,19 +416,13 @@ window.Modernizr = (function( window, document, undefined ) { return !!(Modernizr['canvas'] && is(document.createElement('canvas').getContext('2d').fillText, 'function')); }; - // this test initiates a new webgl context. - // webk.it/70117 is tracking a legit feature detect proposal - + // webk.it/70117 is tracking a legit WebGL feature detect proposal + + // We do a soft detect which may false positive in order to avoid + // an expensive context creation: bugzil.la/732441 + tests['webgl'] = function() { - try { - var canvas = document.createElement('canvas'), - ret; - ret = !!(window.WebGLRenderingContext && (canvas.getContext('experimental-webgl') || canvas.getContext('webgl'))); - canvas = undefined; - } catch (e){ - ret = false; - } - return ret; + return !!window.WebGLRenderingContext; }; /* @@ -453,32 +440,41 @@ window.Modernizr = (function( window, document, undefined ) { */ tests['touch'] = function() { - return Modernizr['touch']; + var bool; + + if(('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) { + bool = true; + } else { + injectElementWithStyles(['@media (',prefixes.join('touch-enabled),('),mod,')','{#modernizr{top:9px;position:absolute}}'].join(''), function( node ) { + bool = node.offsetTop === 9; + }); + } + + return bool; }; - /** - * 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: - * code.google.com/p/geo-location-javascript/ - * or view a fallback solution using google's geo API: - * gist.github.com/366184 - */ + + // geolocation is often considered a trivial feature detect... + // Turns out, it's quite tricky to get right: + // + // Using !!navigator.geolocation does two things we don't want. It: + // 1. Leaks memory in IE9: github.com/Modernizr/Modernizr/issues/513 + // 2. Disables page caching in WebKit: webk.it/43956 + // + // Meanwhile, in Firefox < 8, an about:config setting could expose + // a false positive that would throw an exception: bugzil.la/688158 + tests['geolocation'] = function() { - return !!navigator.geolocation; + return 'geolocation' in navigator; }; - // Per 1.6: - // This used to be Modernizr.crosswindowmessaging but the longer - // name has been deprecated in favor of a shorter and property-matching one. - // The old API is still available in 1.6, but as of 2.0 will throw a warning, - // and in the first release thereafter disappear entirely. + tests['postmessage'] = function() { return !!window.postMessage; }; - // Chrome incognito mode used to throw an exception when using openDatabase + // Chrome incognito mode used to throw an exception when using openDatabase // It doesn't anymore. tests['websqldatabase'] = function() { return !!window.openDatabase; @@ -489,7 +485,7 @@ window.Modernizr = (function( window, document, undefined ) { // - Firefox shipped moz_indexedDB before FF4b9, but since then has been mozIndexedDB // For speed, we don't test the legacy (and beta-only) indexedDB tests['indexedDB'] = function() { - return !!testPropsAll("indexedDB",window); + return !!testPropsAll("indexedDB", window); }; // documentMode logic from YUI to filter out IE8 Compat Mode @@ -512,15 +508,12 @@ window.Modernizr = (function( window, document, undefined ) { return ('draggable' in div) || ('ondragstart' in div && 'ondrop' in div); }; - // FIXME: Once FF10 is sunsetted, we can drop prefixed MozWebSocket - // bugzil.la/695635 + // FF3.6 was EOL'ed on 4/24/12, but the ESR version of FF10 + // will be supported until FF19 (2/12/13), at which time, ESR becomes FF17. + // FF10 still uses prefixes, so check for it until then. + // for more ESR info, see: mozilla.org/en-US/firefox/organizations/faq/ tests['websockets'] = function() { - for ( var i = -1, len = cssomPrefixes.length; ++i < len; ){ - if ( window[cssomPrefixes[i] + 'WebSocket'] ){ - return true; - } - } - return 'WebSocket' in window; + return 'WebSocket' in window || 'MozWebSocket' in window; }; @@ -552,19 +545,13 @@ window.Modernizr = (function( window, document, undefined ) { // If the UA supports multiple backgrounds, there should be three occurrences // of the string "url(" in the return value for elemStyle.background - return /(url\s*\(.*?){3}/.test(mStyle.background); + return (/(url\s*\(.*?){3}/).test(mStyle.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. + // this will false positive in Opera Mini + // github.com/Modernizr/Modernizr/issues/396 tests['backgroundsize'] = function() { return testPropsAll('backgroundSize'); @@ -603,11 +590,11 @@ window.Modernizr = (function( window, document, undefined ) { // The non-literal . in this regex is intentional: // German Chrome returns this value as 0,55 // github.com/Modernizr/Modernizr/issues/#issue/59/comment/516632 - return /^0.55$/.test(mStyle.opacity); + return (/^0.55$/).test(mStyle.opacity); }; - // Note, Android < 4 will pass this test, but can only animate + // Note, Android < 4 will pass this test, but can only animate // a single property at a time // daneden.me/2011/12/putting-up-with-androids-bullshit/ tests['cssanimations'] = function() { @@ -635,9 +622,9 @@ window.Modernizr = (function( window, document, undefined ) { setCss( // legacy webkit syntax (FIXME: remove when syntax not in use anymore) - (str1 + '-webkit- '.split(' ').join(str2 + str1) - // standard syntax // trailing 'background-image:' - + prefixes.join(str3 + str1)).slice(0, -str1.length) + (str1 + '-webkit- '.split(' ').join(str2 + str1) + + // standard syntax // trailing 'background-image:' + prefixes.join(str3 + str1)).slice(0, -str1.length) ); return contains(mStyle.backgroundImage, 'gradient'); @@ -665,8 +652,10 @@ window.Modernizr = (function( window, document, undefined ) { if ( ret && 'webkitPerspective' in docElement.style ) { // Webkit allows this media query to succeed only if the feature is enabled. - // `@media (transform-3d),(-o-transform-3d),(-moz-transform-3d),(-ms-transform-3d),(-webkit-transform-3d),(modernizr){ ... }` - ret = Modernizr['csstransforms3d']; + // `@media (transform-3d),(-webkit-transform-3d){ ... }` + injectElementWithStyles('@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}', function( node, rule ) { + ret = node.offsetLeft === 9 && node.offsetHeight === 3; + }); } return ret; }; @@ -681,15 +670,33 @@ window.Modernizr = (function( window, document, undefined ) { // @font-face detection routine by Diego Perini // javascript.nwbox.com/CSSSupport/ - // false positives in WebOS: github.com/Modernizr/Modernizr/issues/342 + // false positives: + // WebOS github.com/Modernizr/Modernizr/issues/342 + // WP7 github.com/Modernizr/Modernizr/issues/538 tests['fontface'] = function() { - return Modernizr['fontface']; + var bool; + + injectElementWithStyles('@font-face {font-family:"font";src:url("https://")}', function( node, rule ) { + var style = document.getElementById('smodernizr'), + sheet = style.sheet || style.styleSheet, + cssText = sheet ? (sheet.cssRules && sheet.cssRules[0] ? sheet.cssRules[0].cssText : sheet.cssText || '') : ''; + + bool = /src/i.test(cssText) && cssText.indexOf(rule.split(' ')[0]) === 0; + }); + + return bool; }; /*>>fontface*/ // CSS generated content detection tests['generatedcontent'] = function() { - return Modernizr['generatedcontent']; + var bool; + + injectElementWithStyles(['#modernizr:after{content:"',smile,'";visibility:hidden}'].join(''), function( node ) { + bool = node.offsetHeight >= 1; + }); + + return bool; }; @@ -711,20 +718,21 @@ window.Modernizr = (function( window, document, undefined ) { tests['video'] = function() { var elem = document.createElement('video'), bool = false; - + // IE9 Running on Windows Server SKU can cause an exception to be thrown, bug #224 try { if ( bool = !!elem.canPlayType ) { bool = new Boolean(bool); bool.ogg = elem.canPlayType('video/ogg; codecs="theora"') .replace(/^no$/,''); + // Without QuickTime, this value will be `undefined`. github.com/Modernizr/Modernizr/issues/546 bool.h264 = elem.canPlayType('video/mp4; codecs="avc1.42E01E"') .replace(/^no$/,''); bool.webm = elem.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,''); } - + } catch(e) { } - + return bool; }; @@ -732,7 +740,7 @@ window.Modernizr = (function( window, document, undefined ) { var elem = document.createElement('audio'), bool = false; - try { + try { if ( bool = !!elem.canPlayType ) { bool = new Boolean(bool); bool.ogg = elem.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,''); @@ -742,11 +750,11 @@ window.Modernizr = (function( window, document, undefined ) { // developer.mozilla.org/En/Media_formats_supported_by_the_audio_and_video_elements // bit.ly/iphoneoscodecs bool.wav = elem.canPlayType('audio/wav; codecs="1"') .replace(/^no$/,''); - bool.m4a = ( elem.canPlayType('audio/x-m4a;') || + bool.m4a = ( elem.canPlayType('audio/x-m4a;') || elem.canPlayType('audio/aac;')) .replace(/^no$/,''); } } catch(e) { } - + return bool; }; @@ -757,7 +765,7 @@ window.Modernizr = (function( window, document, undefined ) { // `('localStorage' in window) && ` test first because otherwise Firefox will // throw bugzil.la/365772 if cookies are disabled - // Also in iOS5 Private Browsing mode, attepting to use localStorage.setItem + // Also in iOS5 Private Browsing mode, attempting to use localStorage.setItem // will throw the exception: // QUOTA_EXCEEDED_ERRROR DOM Exception 22. // Peculiarly, getItem and removeItem calls do not throw. @@ -826,17 +834,19 @@ window.Modernizr = (function( window, document, undefined ) { return !!document.createElementNS && /SVGClipPath/.test(toString.call(document.createElementNS(ns.svg, 'clipPath'))); }; + /*>>webforms*/ // input features and input types go directly onto the ret object, bypassing the tests loop. // Hold this guy to execute in a moment. function webforms() { + /*>>input*/ // 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: // miketaylr.com/code/input-type-attr.html // spec: www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html#input-type-attr-summary - - // Only input placeholder is tested while textarea's placeholder is not. + + // Only input placeholder is tested while textarea's placeholder is not. // Currently Safari 4 and Opera 11 have support only for the input placeholder // Both tests are available in feature-detects/forms-placeholder.js Modernizr['input'] = (function( props ) { @@ -850,7 +860,9 @@ window.Modernizr = (function( window, document, undefined ) { } return attrs; })('autocomplete autofocus list placeholder max min multiple pattern required step'.split(' ')); + /*>>input*/ + /*>>inputtypes*/ // 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 @@ -887,7 +899,7 @@ window.Modernizr = (function( window, document, undefined ) { docElement.removeChild(inputElem); } else if ( /^(search|tel)$/.test(inputElemType) ){ - // Spec doesnt define any special parsing or detectable UI + // Spec doesn't define any special parsing or detectable UI // behaviors so we pass these through as true // Interestingly, opera fails the earlier test, so it doesn't @@ -897,14 +909,6 @@ window.Modernizr = (function( window, document, undefined ) { // Real url and email support comes with prebaked validation. bool = inputElem.checkValidity && inputElem.checkValidity() === false; - } else if ( /^color$/.test(inputElemType) ) { - // chuck into DOM and force reflow for Opera bug in 11.00 - // github.com/Modernizr/Modernizr/issues#issue/159 - docElement.appendChild(inputElem); - docElement.offsetWidth; - bool = inputElem.value != smile; - docElement.removeChild(inputElem); - } else { // If the upgraded input compontent rejects the :) text, we got a winner bool = inputElem.value != smile; @@ -915,7 +919,9 @@ window.Modernizr = (function( window, document, undefined ) { } return inputs; })('search tel url email datetime date month week time datetime-local number range color'.split(' ')); + /*>>inputtypes*/ } + /*>>webforms*/ // End of test definitions @@ -926,7 +932,7 @@ window.Modernizr = (function( window, document, undefined ) { // Run through all tests and detect their support in the current UA. // todo: hypothetically we could be doing an array of tests and use a basic loop here. for ( var feature in tests ) { - if ( hasOwnProperty(tests, feature) ) { + if ( hasOwnProp(tests, feature) ) { // run the test, throw the return value into the Modernizr, // then based on that boolean, define an appropriate className // and push it into an array of classes we'll join later. @@ -937,8 +943,10 @@ window.Modernizr = (function( window, document, undefined ) { } } + /*>>webforms*/ // input tests need to run. Modernizr.input || webforms(); + /*>>webforms*/ /** @@ -952,7 +960,7 @@ window.Modernizr = (function( window, document, undefined ) { Modernizr.addTest = function ( feature, test ) { if ( typeof feature == 'object' ) { for ( var key in feature ) { - if ( hasOwnProperty( feature, key ) ) { + if ( hasOwnProp( feature, key ) ) { Modernizr.addTest( key, feature[ key ] ); } } @@ -971,7 +979,9 @@ window.Modernizr = (function( window, document, undefined ) { test = typeof test == 'function' ? test() : test; - docElement.className += ' ' + (test ? '' : 'no-') + feature; + if (enableClasses) { + docElement.className += ' ' + (test ? '' : 'no-') + feature; + } Modernizr[feature] = test; } @@ -984,49 +994,60 @@ window.Modernizr = (function( window, document, undefined ) { setCss(''); modElem = inputElem = null; - //>>BEGIN IEPP - // Enable HTML 5 elements for styling in IE & add HTML5 css - /*! HTML5 Shiv v3.4 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed */ + /*>>shiv*/ + /*! HTML5 Shiv v3.6 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed */ ;(function(window, document) { - + /*jshint evil:true */ /** Preset options */ var options = window.html5 || {}; - + /** Used to skip problem elements */ - var reSkip = /^<|^(?:button|form|map|select|textarea)$/i; - + var reSkip = /^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i; + + /** Not all elements can be cloned in IE (this list can be shortend) **/ + var saveClones = /^<|^(?:a|b|button|code|div|fieldset|form|h1|h2|h3|h4|h5|h6|i|iframe|img|input|label|li|link|ol|option|p|param|q|script|select|span|strong|style|table|tbody|td|textarea|tfoot|th|thead|tr|ul)$/i; + /** Detect whether the browser supports default html5 styles */ var supportsHtml5Styles; - + + /** Name of the expando, to work with multiple documents or to re-shiv one document */ + var expando = '_html5shiv'; + + /** The id for the the documents expando */ + var expanID = 0; + + /** Cached data for each document */ + var expandoData = {}; + /** Detect whether the browser supports unknown elements */ var supportsUnknownElements; - + (function() { - var a = document.createElement('a'); - - a.innerHTML = ''; - - //if the hidden property is implemented we can assume, that the browser supports HTML5 Styles - supportsHtml5Styles = ('hidden' in a); - supportsUnknownElements = a.childNodes.length == 1 || (function() { - // assign a false positive if unable to shiv - try { - (document.createElement)('a'); - } catch(e) { - return true; - } - var frag = document.createDocumentFragment(); - return ( - typeof frag.cloneNode == 'undefined' || - typeof frag.createDocumentFragment == 'undefined' || - typeof frag.createElement == 'undefined' - ); - }()); - + try { + var a = document.createElement('a'); + a.innerHTML = ''; + //if the hidden property is implemented we can assume, that the browser supports basic HTML5 Styles + supportsHtml5Styles = ('hidden' in a); + + supportsUnknownElements = a.childNodes.length == 1 || (function() { + // assign a false positive if unable to shiv + (document.createElement)('a'); + var frag = document.createDocumentFragment(); + return ( + typeof frag.cloneNode == 'undefined' || + typeof frag.createDocumentFragment == 'undefined' || + typeof frag.createElement == 'undefined' + ); + }()); + } catch(e) { + supportsHtml5Styles = true; + supportsUnknownElements = true; + } + }()); - + /*--------------------------------------------------------------------------*/ - + /** * Creates a style sheet with the given CSS text and adds it to the document. * @private @@ -1037,11 +1058,11 @@ window.Modernizr = (function( window, document, undefined ) { function addStyleSheet(ownerDocument, cssText) { var p = ownerDocument.createElement('p'), parent = ownerDocument.getElementsByTagName('head')[0] || ownerDocument.documentElement; - + p.innerHTML = 'x'; return parent.insertBefore(p.lastChild, parent.firstChild); } - + /** * Returns the value of `html5.elements` as an array. * @private @@ -1051,45 +1072,123 @@ window.Modernizr = (function( window, document, undefined ) { var elements = html5.elements; return typeof elements == 'string' ? elements.split(' ') : elements; } - + + /** + * Returns the data associated to the given document + * @private + * @param {Document} ownerDocument The document. + * @returns {Object} An object of data. + */ + function getExpandoData(ownerDocument) { + var data = expandoData[ownerDocument[expando]]; + if (!data) { + data = {}; + expanID++; + ownerDocument[expando] = expanID; + expandoData[expanID] = data; + } + return data; + } + + /** + * returns a shived element for the given nodeName and document + * @memberOf html5 + * @param {String} nodeName name of the element + * @param {Document} ownerDocument The context document. + * @returns {Object} The shived element. + */ + function createElement(nodeName, ownerDocument, data){ + if (!ownerDocument) { + ownerDocument = document; + } + if(supportsUnknownElements){ + return ownerDocument.createElement(nodeName); + } + if (!data) { + data = getExpandoData(ownerDocument); + } + var node; + + if (data.cache[nodeName]) { + node = data.cache[nodeName].cloneNode(); + } else if (saveClones.test(nodeName)) { + node = (data.cache[nodeName] = data.createElem(nodeName)).cloneNode(); + } else { + node = data.createElem(nodeName); + } + + // Avoid adding some elements to fragments in IE < 9 because + // * Attributes like `name` or `type` cannot be set/changed once an element + // is inserted into a document/fragment + // * Link elements with `src` attributes that are inaccessible, as with + // a 403 response, will cause the tab/window to crash + // * Script elements appended to fragments will execute when their `src` + // or `text` property is set + return node.canHaveChildren && !reSkip.test(nodeName) ? data.frag.appendChild(node) : node; + } + + /** + * returns a shived DocumentFragment for the given document + * @memberOf html5 + * @param {Document} ownerDocument The context document. + * @returns {Object} The shived DocumentFragment. + */ + function createDocumentFragment(ownerDocument, data){ + if (!ownerDocument) { + ownerDocument = document; + } + if(supportsUnknownElements){ + return ownerDocument.createDocumentFragment(); + } + data = data || getExpandoData(ownerDocument); + var clone = data.frag.cloneNode(), + i = 0, + elems = getElements(), + l = elems.length; + for(;i>END IEPP + }(this, document)); + /*>>shiv*/ // Assign private properties to the return object with prefix Modernizr._version = version; // expose these for the plugin API. Look in the source for how to join() them against your input + /*>>prefixes*/ Modernizr._prefixes = prefixes; + /*>>prefixes*/ + /*>>domprefixes*/ Modernizr._domPrefixes = domPrefixes; Modernizr._cssomPrefixes = cssomPrefixes; - + /*>>domprefixes*/ + + /*>>mq*/ // Modernizr.mq tests a given media query, live against the current state of the window // A few important notes: // * If a browser does not support media queries at all (eg. oldIE) the mq() will always return false // * A max-width or orientation query will be evaluated against the current state, which may change later. - // * You must specify values. Eg. If you are testing support for the min-width media query use: + // * You must specify values. Eg. If you are testing support for the min-width media query use: // Modernizr.mq('(min-width:0)') // usage: // Modernizr.mq('only screen and (max-width:768)') - Modernizr.mq = testMediaQuery; - + Modernizr.mq = testMediaQuery; + /*>>mq*/ + + /*>>hasevent*/ // Modernizr.hasEvent() detects support for a given event, with an optional element to test on // Modernizr.hasEvent('gesturestart', elem) - Modernizr.hasEvent = isEventSupported; + Modernizr.hasEvent = isEventSupported; + /*>>hasevent*/ + /*>>testprop*/ // Modernizr.testProp() investigates whether a given style property is recognized // Note that the property names must be provided in the camelCase variant. // Modernizr.testProp('pointerEvents') Modernizr.testProp = function(prop){ return testProps([prop]); - }; + }; + /*>>testprop*/ + /*>>testallprops*/ // Modernizr.testAllProps() investigates whether a given style property, // or any of its vendor-prefixed variants, is recognized // Note that the property names must be provided in the camelCase variant. - // Modernizr.testAllProps('boxSizing') - Modernizr.testAllProps = testPropsAll; + // Modernizr.testAllProps('boxSizing') + Modernizr.testAllProps = testPropsAll; + /*>>testallprops*/ - + /*>>teststyles*/ // Modernizr.testStyles() allows you to add custom styles to the document and test an element afterwards // Modernizr.testStyles('#modernizr { position:absolute }', function(elem, rule){ ... }) - Modernizr.testStyles = injectElementWithStyles; + Modernizr.testStyles = injectElementWithStyles; + /*>>teststyles*/ + /*>>prefixed*/ // Modernizr.prefixed() returns the prefixed or nonprefixed property name variant of your input // Modernizr.prefixed('boxSizing') // 'MozBoxSizing' - + // Properties must be passed as dom-style camelcase, rather than `box-sizing` hypentated style. // Return values will also be the camelCase variant, if you need to translate that to hypenated style use: // // str.replace(/([A-Z])/g, function(str,m1){ return '-' + m1.toLowerCase(); }).replace(/^ms-/,'-ms-'); - + // If you're trying to ascertain which transition end event to bind to, you might do something like... - // + // // var transEndEventNames = { // 'WebkitTransition' : 'webkitTransitionEnd', // 'MozTransition' : 'transitionend', // 'OTransition' : 'oTransitionEnd', - // 'msTransition' : 'MsTransitionEnd', + // 'msTransition' : 'MSTransitionEnd', // 'transition' : 'transitionend' // }, // transEndEventName = transEndEventNames[ Modernizr.prefixed('transition') ]; - + Modernizr.prefixed = function(prop, obj, elem){ if(!obj) { return testPropsAll(prop, 'pfx'); @@ -1251,14 +1368,16 @@ window.Modernizr = (function( window, document, undefined ) { return testPropsAll(prop, obj, elem); } }; + /*>>prefixed*/ - + /*>>cssclasses*/ // Remove "no-js" class from element, if it exists: docElement.className = docElement.className.replace(/(^|\s)no-js(\s|$)/, '$1$2') + - + // Add the new classes to the element. (enableClasses ? ' js ' + classes.join(' ') : ''); + /*>>cssclasses*/ return Modernizr; diff --git a/readme.md b/readme.md index 2b56ecae01..e025d02896 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,4 @@ -Modernizr +Modernizr [![Build Status](https://secure.travis-ci.org/Modernizr/Modernizr.png?branch=master)](http://travis-ci.org/Modernizr/Modernizr) ========= ### a JavaScript library allowing you to use CSS3 & HTML5 while maintaining control over unsupported browsers diff --git a/test/basic.html b/test/basic.html new file mode 100644 index 0000000000..21ea6dc4f2 --- /dev/null +++ b/test/basic.html @@ -0,0 +1,65 @@ + + + + + Modernizr Test Suite + + + + + + + + + + + + + + + + + + +

Modernizr Test Suite

+

+
+

+ +
    + +
    +
    + + +
    + +
    + + + + diff --git a/test/caniuse_files/apng_test.png b/test/caniuse_files/apng_test.png index 5f49ed100a..f359d3c0cb 100644 Binary files a/test/caniuse_files/apng_test.png and b/test/caniuse_files/apng_test.png differ diff --git a/test/caniuse_files/before-after.png b/test/caniuse_files/before-after.png index 77c7fede86..7a94c4b1e5 100644 Binary files a/test/caniuse_files/before-after.png and b/test/caniuse_files/before-after.png differ diff --git a/test/caniuse_files/mathml_ref.png b/test/caniuse_files/mathml_ref.png index 4e69935974..1633e58db6 100644 Binary files a/test/caniuse_files/mathml_ref.png and b/test/caniuse_files/mathml_ref.png differ diff --git a/test/caniuse_files/png_alpha_result.png b/test/caniuse_files/png_alpha_result.png index 0acd3acf73..45fdf06be9 100644 Binary files a/test/caniuse_files/png_alpha_result.png and b/test/caniuse_files/png_alpha_result.png differ diff --git a/test/caniuse_files/red30x30.png b/test/caniuse_files/red30x30.png index 81c13017ab..561c8d2d8b 100644 Binary files a/test/caniuse_files/red30x30.png and b/test/caniuse_files/red30x30.png differ diff --git a/test/caniuse_files/ruby.png b/test/caniuse_files/ruby.png index 37539adb6a..4fe06dd37a 100644 Binary files a/test/caniuse_files/ruby.png and b/test/caniuse_files/ruby.png differ diff --git a/test/caniuse_files/stroked-text.png b/test/caniuse_files/stroked-text.png index 35ea0229a0..e75890faa5 100644 Binary files a/test/caniuse_files/stroked-text.png and b/test/caniuse_files/stroked-text.png differ diff --git a/test/caniuse_files/svg-html-blur.png b/test/caniuse_files/svg-html-blur.png index 6d55056362..549b29757a 100644 Binary files a/test/caniuse_files/svg-html-blur.png and b/test/caniuse_files/svg-html-blur.png differ diff --git a/test/caniuse_files/svg_blur.png b/test/caniuse_files/svg_blur.png index 90f1a32ee4..17cb6e3a8d 100644 Binary files a/test/caniuse_files/svg_blur.png and b/test/caniuse_files/svg_blur.png differ diff --git a/test/caniuse_files/table.png b/test/caniuse_files/table.png index ce362248ab..e9b9b9d772 100644 Binary files a/test/caniuse_files/table.png and b/test/caniuse_files/table.png differ diff --git a/test/caniuse_files/text-shadow1.png b/test/caniuse_files/text-shadow1.png index 0643234855..47b3ceaf08 100644 Binary files a/test/caniuse_files/text-shadow1.png and b/test/caniuse_files/text-shadow1.png differ diff --git a/test/caniuse_files/text-shadow2.png b/test/caniuse_files/text-shadow2.png index c58dd8ff38..f335189c4c 100644 Binary files a/test/caniuse_files/text-shadow2.png and b/test/caniuse_files/text-shadow2.png differ diff --git a/test/caniuse_files/windsong_font.png b/test/caniuse_files/windsong_font.png index 7fde5067a7..b983d2acab 100644 Binary files a/test/caniuse_files/windsong_font.png and b/test/caniuse_files/windsong_font.png differ diff --git a/test/index.html b/test/index.html index c3b0e628af..587f0c87e4 100644 --- a/test/index.html +++ b/test/index.html @@ -30,22 +30,23 @@ #caniusetrigger { font-size: 38px; font-family: monospace; display:block; } - + + - + - + - + - + @@ -62,16 +63,16 @@


    - +
    - - + +
    JSON.stringify(Modernizr)
    - Show the Ref Tests from Caniuse and Modernizr @@ -79,25 +80,25 @@
    JSON.stringify(Modernizr)
    - - + + - + diff --git a/test/js/basic.html b/test/js/basic.html new file mode 100644 index 0000000000..9f378a9601 --- /dev/null +++ b/test/js/basic.html @@ -0,0 +1,65 @@ + + + + + Modernizr Test Suite + + + + + + + + + + + + + + + + + + +

    Modernizr Test Suite

    +

    +
    +

    + +
      + +
      +
      + + +
      + +
      + + + + diff --git a/test/js/server.js b/test/js/server.js new file mode 100644 index 0000000000..6e7465a301 --- /dev/null +++ b/test/js/server.js @@ -0,0 +1,10 @@ +var connect = require('connect'), + args = process.argv.slice(2), + folder = args[0] || '/../../', + port = args[1] || '80'; + +var server = connect.createServer( + connect.static(__dirname + folder) +).listen(port); + +console.log("Server started on port %s in %s", port, folder); \ No newline at end of file diff --git a/test/js/unit-caniuse.js b/test/js/unit-caniuse.js index be13131b6f..0e89031fa3 100644 --- a/test/js/unit-caniuse.js +++ b/test/js/unit-caniuse.js @@ -2,7 +2,7 @@ var myscript = document.createElement('script'), ref = document.getElementsByTagName('script')[0]; - + myscript.src = 'http://caniuse.com/jsonp.php?callback=caniusecb'; setTimeout(function(){ @@ -16,29 +16,29 @@ var map = { borderradius : 'border-radius', canvas : 'canvas', canvastext : 'canvas-text', - cssanimations : 'css-animation', - boxshadow : 'css-boxshadow', + cssanimations : 'css-animation', + boxshadow : 'css-boxshadow', cssgradients : 'css-gradients', opacity : 'css-opacity', - cssreflections : 'css-reflections', + cssreflections : 'css-reflections', textshadow : 'css-textshadow', csstransitions : 'css-transitions', hsla : 'css3-colors', rgba : 'css3-colors', - draganddrop : 'dragndrop', - flexbox : 'flexbox', + draganddrop : 'dragndrop', + flexbox : 'flexbox', fontface : 'fontface', geolocation : 'geolocation', - hashchange : 'hashchange', + hashchange : 'hashchange', history : 'history', - indexeddb : 'indexeddb', + indexeddb : 'indexeddb', multiplebgs : 'multibackgrounds', csscolumns : 'multicolumn', localstorage : 'namevalue-storage', applicationcache : 'offline-apps', websqldatabase : 'sql-storage', svg : 'svg', - touch : 'touch', + touch : 'touch', csstransforms : 'transforms2d', csstransforms3d : 'transforms3d', video: 'video', @@ -49,7 +49,7 @@ var map = { }; window.caniusecb = function(scriptdata) { - + window.doo = scriptdata; // quit if JSONSelect didn't make it. @@ -61,50 +61,50 @@ window.caniusecb = function(scriptdata) { ua = uaparse(navigator.userAgent), // match the UA from uaparser into the browser used by caniuse - browserKey = JSONSelect.match('.agents .browser', scriptdata).indexOf(ua.family), + browserKey = JSONSelect.match('.agents .browser', scriptdata).indexOf(ua.family), currBrowser = Object.keys(scriptdata.agents)[browserKey]; - - + + // translate 'y' 'n' or 'a' into a boolean that Modernizr uses function bool(ciuresult){ if (ciuresult == 'y' || ciuresult == 'a') return true; // 'p' is for polyfill if (ciuresult == 'n' || ciuresult == 'p') return false; throw 'unknown return value from can i use'; - } - + } + function testify(o){ - + var ciubool = bool(o.ciuresult); // caniuse says audio/video are yes/no, Modernizr has more detail which we'll dumb down. if (~TEST.audvid.indexOf(o.feature)) o.result = !!o.result; - + // if caniuse gave us a 'partial', lets let it pass with a note. if (o.ciuresult == 'a'){ - return ok(true, - o.browser + o.version + ': Caniuse reported partial support for ' + o.ciufeature + + return ok(true, + o.browser + o.version + ': Caniuse reported partial support for ' + o.ciufeature + '. So.. Modernizr\'s ' + o.result + ' is good enough...' ); } - - + + // change the *documented* false positives if ((o.feature == 'textshadow' && o.browser == 'firefox' && o.version == 3) && ciubool == false ) { ciubool = o.fp = true; } - + // where we actually do most our assertions - equals(o.result, ciubool, - o.browser + o.version + ': Caniuse result for ' + o.ciufeature + - ' matches Modernizr\'s ' + (o.fp ? '*false positive*' : 'result') + ' for ' + o.feature + equals(o.result, ciubool, + o.browser + o.version + ': Caniuse result for ' + o.ciufeature + + ' matches Modernizr\'s ' + (o.fp ? '*false positive*' : 'result') + ' for ' + o.feature ); } - - + + module('caniuse.com data matches', { setup:function() { }, @@ -114,40 +114,40 @@ window.caniusecb = function(scriptdata) { test("we match caniuse data", function() { - + for (var feature in Modernizr){ - + var ciufeatname = map[feature]; - + if (ciufeatname === undefined) continue; - + var ciufeatdata = testdata[ciufeatname]; - + if (ciufeatdata === undefined) throw 'unknown key of caniusedata'; // get results for this feature for all versions of this browser var browserResults = ciufeatdata.stats[currBrowser]; - + // let's get our versions in order.. var minorver = ua.minor && // caniuse doesn't use two digit minors ua.minor.toString().replace(/(\d)\d$/,'$1'), // but opera does. - + majorminor = (ua.major + '.' + minorver) // opera gets grouped in some cases by caniuse .replace(/(9\.(6|5))/ , ua.family == 'opera' ? '9.5-9.6' : "$1") .replace(/(10\.(0|1))/, ua.family == 'opera' ? '10.0-10.1' : "$1"), - + mmResult = browserResults[majorminor], mResult = browserResults[ua.major]; - - + + // check it against the major.minor: eg. FF 3.6 if (mmResult && mmResult != 'u'){ // 'y' 'n' or 'a' - + // data ends w/ ` x` if its still prefixed in the imp mmResult = mmResult.replace(' x',''); - - // match it against our data. + + // match it against our data. testify({ feature : feature , ciufeature : ciufeatname , result : Modernizr[feature] @@ -158,13 +158,13 @@ window.caniusecb = function(scriptdata) { continue; // don't check the major version } - + // check it against just the major version: eg. FF 3 - if (mResult){ + if (mResult){ // unknown support from caniuse... He would probably like to know our data, though! if (mResult == 'u') continue; - + // data ends w/ ` x` if its still prefixed in the imp mResult = mResult.replace(' x',''); @@ -176,12 +176,12 @@ window.caniusecb = function(scriptdata) { , version : ua.major }); - + } - + } // for in loop - + }); // eo test() - + }; // eo caniusecallback() diff --git a/test/js/unit.js b/test/js/unit.js index 6c5952f366..8a582496be 100644 --- a/test/js/unit.js +++ b/test/js/unit.js @@ -1,3 +1,22 @@ +QUnit.begin = function() { + console.log("Starting test suite"); + console.log("================================================\n"); +}; + +QUnit.moduleDone = function(opts) { + if(opts.failed === 0) { + console.log("\u2714 All tests passed in '"+opts.name+"' module"); + } else { + console.log("\u2716 "+ opts.failed +" tests failed in '"+opts.name+"' module"); + } +}; + +QUnit.done = function(opts) { + console.log("\n================================================"); + console.log("Tests completed in "+opts.runtime+" milliseconds"); + console.log(opts.passed + " tests of "+opts.total+" passed, "+opts.failed+" failed."); +}; + module('Basics', { setup:function() { }, @@ -6,52 +25,97 @@ module('Basics', { }); test("globals set up", function() { - + ok(window.Modernizr, 'global modernizr object created'); - + // this comes from kangax's detect-global.js - + var globArr = Object.keys(__globals); - + // remove Modernizr and html5 var leakedGlobArr = [''].concat(globArr).concat(['']) .join(',') .replace(',Modernizr','').replace(',html5','') .replace(/,script\w+/,'') // placed by jQuery .split(','); - + equals('', leakedGlobArr.pop(), 'retrieved my empty item from the end'); equals('', leakedGlobArr.shift(), 'retrieved my empty item from the front'); - equals(leakedGlobArr.toString(), [].toString(), 'no global variables should leak (other than Modernizr and iepp)'); + equals(leakedGlobArr.toString(), [].toString(), 'no global variables should leak (other than Modernizr and iepp)'); }); test("bind is implemented", function() { - - ok(Function.prototype.bind, 'bind is a member of Function.prototype'); - - var a = function(){ - return this.modernizr; - }; - a = a.bind({modernizr: 'just awsome'}); - equals("just awsome", a(), 'bind works as expected'); + ok(Function.prototype.bind, 'bind is a member of Function.prototype'); + + var a = function(){ + return this.modernizr; + }; + a = a.bind({modernizr: 'just awsome'}); + + equals("just awsome", a(), 'bind works as expected'); + + + // thank you webkit layoutTests + + + var result; + + function F(x, y) + { + result = this + " -> x:" + x + ", y:" + y; + } + + G = F.bind("'a'", "'b'"); + H = G.bind("'Cannot rebind this!'", "'c'"); + + G(1,2); + equal(result, "\'a\' -> x:\'b\', y:1"); + H(1,2); + equal(result, "\'a\' -> x:\'b\', y:\'c\'"); + + var f = new F(1,2); + equal(result, "[object Object] -> x:1, y:2"); + var g = new G(1,2); + equal(result, "[object Object] -> x:\'b\', y:1"); + var h = new H(1,2); + equal(result, "[object Object] -> x:\'b\', y:\'c\'"); + + ok(f instanceof F, "f instanceof F"); + ok(g instanceof F, "g instanceof F"); + ok(h instanceof F, "h instanceof F"); + + // Bound functions don't have a 'prototype' property. + ok("prototype" in F, '"prototype" in F'); + + // The object passed to bind as 'this' must be callable. + raises(function(){ + Function.bind.call(undefined); + }); + + // Objects that allow call but not construct can be bound, but should throw if used with new. + var abcAt = String.prototype.charAt.bind("abc"); + equals(abcAt(1), "b", 'Objects that allow call but not construct can be bound...'); + + equals(1, Function.bind.length, 'it exists'); + }); test("document.documentElement is valid and correct",1, function() { - equals(document.documentElement,document.getElementsByTagName('html')[0]); + equals(document.documentElement,document.getElementsByTagName('html')[0]); }); test("no-js class is gone.", function() { - + ok(!/(?:^|\s)no-js(?:^|\s)/.test(document.documentElement.className), 'no-js class is gone'); - + ok(/(?:^|\s)js(?:^|\s)/.test(document.documentElement.className), 'html.js class is present'); @@ -65,23 +129,23 @@ test("no-js class is gone.", function() { 'html.i-has-no-js class is still present'); if (document.querySelector){ - ok(document.querySelector('html.js') == document.documentElement, + ok(document.querySelector('html.js') == document.documentElement, "document.querySelector('html.js') matches."); } }); test('html shim worked', function(){ expect(2); - + // the exact test we use in the script var elem = document.getElementsByTagName("section")[0]; elem.id = "html5section"; ok( elem.childNodes.length === 1 , 'unknown elements dont collapse'); - + elem.style.color = 'red'; ok( /red|#ff0000/i.test(elem.style.color), 'unknown elements are styleable') - + }); @@ -94,10 +158,10 @@ module('Modernizr classes and bools', { test('html classes are looking good',function(){ - + var classes = TEST.trim(document.documentElement.className).split(/\s+/); - - var modprops = Object.keys(Modernizr), + + var modprops = Object.keys(Modernizr), newprops = modprops; // decrement for the properties that are private @@ -106,50 +170,50 @@ test('html classes are looking good',function(){ equals(-1, TEST.inArray(item, classes), 'private Modernizr object '+ item +'should not have matching classes'); equals(-1, TEST.inArray('no-' + item, classes), 'private Modernizr object no-'+item+' should not have matching classes'); } - + // decrement for the non-boolean objects // for (var i = -1, len = TEST.inputs.length; ++i < len; ){ // if (Modernizr[TEST.inputs[i]] != undefined) newprops--; // } - + // TODO decrement for the extraclasses - + // decrement for deprecated ones. $.each( TEST.deprecated, function(key, val){ newprops.splice( TEST.inArray(item, newprops), 1); }); - - + + //equals(classes,newprops,'equal number of classes and global object props'); - + if (classes.length !== newprops){ //window.console && console.log(classes, newprops); - + } - + for (var i = 0, len = classes.length, aclass; i = 0) continue; - + ok(Modernizr[prop] === true || Modernizr[prop] === false, 'Modernizr.'+prop+' is a straight up boolean'); - - + + equals(prop,prop.toLowerCase(),'all properties are lowerCase.') } } @@ -186,34 +250,34 @@ test('Modernizr properties are looking good',function(){ test('Modernizr.audio and Modernizr.video',function(){ - + for (var i = -1, len = TEST.audvid.length; ++i < len;){ var prop = TEST.audvid[i]; - + if (Modernizr[prop].toString() == 'true'){ - + ok(Modernizr[prop], 'Modernizr.'+prop+' is truthy.'); equals(Modernizr[prop] == true,true, 'Modernizr.'+prop+' is == true') equals(typeof Modernizr[prop] === 'object',true,'Moderizr.'+prop+' is truly an object'); equals(Modernizr[prop] !== true,true, 'Modernizr.'+prop+' is !== true') - + } else { - + equals(Modernizr[prop] != true,true, 'Modernizr.'+prop+' is != true') } } - - + + }); test('Modernizr results match expected values',function(){ - + // i'm bringing over a few tests from inside Modernizr.js equals(!!document.createElement('canvas').getContext,Modernizr.canvas,'canvas test consistent'); - + equals(!!window.Worker,Modernizr.webworkers,'web workers test consistent') - + }); @@ -226,44 +290,44 @@ module('Modernizr\'s API methods', { }); test('Modernizr.addTest()',22,function(){ - + var docEl = document.documentElement; - - + + Modernizr.addTest('testtrue',function(){ return true; }); - + Modernizr.addTest('testtruthy',function(){ return 100; }); - + Modernizr.addTest('testfalse',function(){ return false; }); - + Modernizr.addTest('testfalsy',function(){ return undefined; }); - + ok(docEl.className.indexOf(' testtrue') >= 0,'positive class added'); equals(Modernizr.testtrue,true,'positive prop added'); - + ok(docEl.className.indexOf(' testtruthy') >= 0,'positive class added'); equals(Modernizr.testtruthy,100,'truthy value is not casted to straight boolean'); - + ok(docEl.className.indexOf(' no-testfalse') >= 0,'negative class added'); equals(Modernizr.testfalse,false,'negative prop added'); - + ok(docEl.className.indexOf(' no-testfalsy') >= 0,'negative class added'); equals(Modernizr.testfalsy,undefined,'falsy value is not casted to straight boolean'); - - - + + + Modernizr.addTest('testcamelCase',function(){ return true; }); - + ok(docEl.className.indexOf(' testcamelCase') === -1, 'camelCase test name toLowerCase()\'d'); @@ -311,7 +375,7 @@ test('Modernizr.addTest()',22,function(){ .addTest('testchainone', true) .addTest({ testchaintwo: true }) .addTest('testchainthree', function(){ return true; }); - + ok( Modernizr.testchainone == Modernizr.testchaintwo == Modernizr.testchainthree, 'addTest is chainable'); @@ -322,10 +386,10 @@ test('Modernizr.addTest()',22,function(){ test('Modernizr.mq: media query testing',function(){ - + var $html = $('html'); $.mobile = {}; - + // from jquery mobile $.mobile.media = (function() { @@ -338,14 +402,14 @@ test('Modernizr.mq: media query testing',function(){ if ( !( query in cache ) ) { var styleBlock = document.createElement('style'), cssrule = "@media " + query + " { #jquery-mediatest { position:absolute; } }"; - //must set type for IE! + //must set type for IE! styleBlock.type = "text/css"; - if (styleBlock.styleSheet){ + if (styleBlock.styleSheet){ styleBlock.styleSheet.cssText = cssrule; - } + } else { styleBlock.appendChild(document.createTextNode(cssrule)); - } + } $html.prepend( fakeBody ).prepend( styleBlock ); cache[ query ] = testDiv.css( "position" ) === "absolute"; @@ -354,35 +418,35 @@ test('Modernizr.mq: media query testing',function(){ return cache[ query ]; }; })(); - - + + ok(Modernizr.mq,'Modernizr.mq() doesn\' freak out.'); - + equals($.mobile.media('only screen'), Modernizr.mq('only screen'),'screen media query matches jQuery mobile\'s result'); - + equals(Modernizr.mq('only all'), Modernizr.mq('only all'), 'Cache hit matches'); - - + + }); test('Modernizr.hasEvent()',function(){ - + ok(typeof Modernizr.hasEvent == 'function','Modernizr.hasEvent() is a function'); - - + + equals(Modernizr.hasEvent('click'), true,'click event is supported'); equals(Modernizr.hasEvent('modernizrcustomevent'), false,'random event is definitely not supported'); - + /* works fine in webkit but not gecko equals( Modernizr.hasEvent('resize', window), !Modernizr.hasEvent('resize', document.createElement('div')), 'Resize is supported in window but not a div, typically...'); */ - + }); @@ -390,35 +454,37 @@ test('Modernizr.hasEvent()',function(){ test('Modernizr.testStyles()',function(){ - + equals(typeof Modernizr.testStyles, 'function','Modernizr.testStyles() is a function'); - - var style = '#modernizr{ width: 9px; height: 4px; color: papayawhip; }'; - + + var style = '#modernizr{ width: 9px; height: 4px; font-size: 0; color: papayawhip; }'; + Modernizr.testStyles(style, function(elem, rule){ equals(style, rule, 'rule passsed back matches what i gave it.') equals(elem.offsetWidth, 9, 'width was set through the style'); equals(elem.offsetHeight, 4, 'height was set through the style'); equals(elem.id, 'modernizr', 'element is indeed the modernizr element'); }); - + }); test('Modernizr._[properties]',function(){ - + equals(6, Modernizr._prefixes.length, 'Modernizr._prefixes has 6 items'); - + equals(4, Modernizr._domPrefixes.length, 'Modernizr.domPrefixes has 4 items'); - + }); test('Modernizr.testProp()',function(){ - + equals(true, Modernizr.testProp('margin'), 'Everyone supports margin'); - + equals(false, Modernizr.testProp('happiness'), 'Nobody supports the happiness style. :('); - + equals(true, Modernizr.testProp('fontSize'), 'Everyone supports fontSize'); + equals(false, Modernizr.testProp('font-size'), 'Nobody supports font-size'); + equals('pointerEvents' in document.createElement('div').style, Modernizr.testProp('pointerEvents'), 'results for `pointer-events` are consistent with a homegrown feature test'); @@ -428,15 +494,17 @@ test('Modernizr.testProp()',function(){ test('Modernizr.testAllProps()',function(){ - + equals(true, Modernizr.testAllProps('margin'), 'Everyone supports margin'); - + equals(false, Modernizr.testAllProps('happiness'), 'Nobody supports the happiness style. :('); + equals(true, Modernizr.testAllProps('fontSize'), 'Everyone supports fontSize'); + equals(false, Modernizr.testAllProps('font-size'), 'Nobody supports font-size'); equals(Modernizr.csstransitions, Modernizr.testAllProps('transition'), 'Modernizr result matches API result: csstransitions'); - + equals(Modernizr.csscolumns, Modernizr.testAllProps('columnCount'), 'Modernizr result matches API result: csscolumns') - + }); @@ -446,7 +514,7 @@ test('Modernizr.testAllProps()',function(){ test('Modernizr.prefixed() - css and DOM resolving', function(){ // https://gist.github.com/523692 - + function gimmePrefix(prop, obj){ var prefixes = ['Moz','Khtml','Webkit','O','ms'], domPrefixes = ['moz','khtml','webkit','o','ms'], @@ -474,12 +542,12 @@ test('Modernizr.prefixed() - css and DOM resolving', function(){ return false; } - - var propArr = ['transition', 'backgroundSize', 'boxSizing', 'borderImage', + + var propArr = ['transition', 'backgroundSize', 'boxSizing', 'borderImage', 'borderRadius', 'boxShadow', 'columnCount']; - - var domPropArr = [{ 'prop': 'requestAnimationFrame', 'obj': window }, - { 'prop': 'querySelectorAll', 'obj': document }, + + var domPropArr = [{ 'prop': 'requestAnimationFrame', 'obj': window }, + { 'prop': 'querySelectorAll', 'obj': document }, { 'prop': 'matchesSelector', 'obj': document.createElement('div') }]; for (var i = -1, len = propArr.length; ++i < len; ){ @@ -492,15 +560,15 @@ test('Modernizr.prefixed() - css and DOM resolving', function(){ ok(!!~Modernizr.prefixed(prop.prop, prop.obj, false).toString().indexOf(gimmePrefix(prop.prop, prop.obj)), 'results for ' + prop.prop + ' match the homebaked prefix finder'); } - - - + + + }); // FIXME: so a few of these are whitelisting for webkit. i'd like to improve that. test('Modernizr.prefixed autobind', function(){ - + var rAFName; // quick sniff to find the local rAF prefixed name. @@ -512,14 +580,14 @@ test('Modernizr.prefixed autobind', function(){ if (rAFName){ // rAF returns a function equals( - 'function', - typeof Modernizr.prefixed('requestAnimationFrame', window), + 'function', + typeof Modernizr.prefixed('requestAnimationFrame', window), "Modernizr.prefixed('requestAnimationFrame', window) returns a function") // unless we false it to a string equals( - rAFName, - Modernizr.prefixed('requestAnimationFrame', window, false), + rAFName, + Modernizr.prefixed('requestAnimationFrame', window, false), "Modernizr.prefixed('requestAnimationFrame', window, false) returns a string (the prop name)") } @@ -530,48 +598,48 @@ test('Modernizr.prefixed autobind', function(){ //returns function equals( - 'function', - typeof fn, + 'function', + typeof fn, "Modernizr.prefixed('matchesSelector', HTMLElement.prototype, document.body) returns a function"); // fn scoping equals( - true, - fn('body'), + true, + fn('body'), "Modernizr.prefixed('matchesSelector', HTMLElement.prototype, document.body) is scoped to the body") } // Webkit only: are there other objects that are prefixed? if (window.webkitNotifications){ - // should be an object. + // should be an object. equals( - 'object', - typeof Modernizr.prefixed('Notifications', window), + 'object', + typeof Modernizr.prefixed('Notifications', window), "Modernizr.prefixed('Notifications') returns an object"); } - // Webkit only: + // Webkit only: if (typeof document.webkitIsFullScreen !== 'undefined'){ // boolean equals( - 'boolean', - typeof Modernizr.prefixed('isFullScreen', document), + 'boolean', + typeof Modernizr.prefixed('isFullScreen', document), "Modernizr.prefixed('isFullScreen') returns a boolean"); } - - // Moz only: + + // Moz only: if (typeof document.mozFullScreen !== 'undefined'){ // boolean equals( - 'boolean', - typeof Modernizr.prefixed('fullScreen', document), + 'boolean', + typeof Modernizr.prefixed('fullScreen', document), "Modernizr.prefixed('fullScreen') returns a boolean"); } @@ -581,13 +649,13 @@ test('Modernizr.prefixed autobind', function(){ // string equals( - 'string', - typeof Modernizr.prefixed('animation', document.body.style), + 'string', + typeof Modernizr.prefixed('animation', document.body.style), "Modernizr.prefixed('animation', document.body.style) returns value of that, as a string"); equals( - animationStyle.toLowerCase(), - Modernizr.prefixed('animation', document.body.style, false).toLowerCase(), + animationStyle.toLowerCase(), + Modernizr.prefixed('animation', document.body.style, false).toLowerCase(), "Modernizr.prefixed('animation', document.body.style, false) returns the (case-normalized) name of the property: webkitanimation"); } @@ -596,7 +664,7 @@ test('Modernizr.prefixed autobind', function(){ false, Modernizr.prefixed('doSomethingAmazing$#$', window), "Modernizr.prefixed('doSomethingAmazing$#$', window) : Gobbledygook with prefixed(str,obj) returns false"); - + equals( false, Modernizr.prefixed('doSomethingAmazing$#$', window, document.body), diff --git a/test/qunit/run-qunit.js b/test/qunit/run-qunit.js new file mode 100644 index 0000000000..5617ee4c18 --- /dev/null +++ b/test/qunit/run-qunit.js @@ -0,0 +1,72 @@ +/** +* Wait until the test condition is true or a timeout occurs. Useful for waiting +* on a server response or for a ui change (fadeIn, etc.) to occur. +* +* @param testFx javascript condition that evaluates to a boolean, +* it can be passed in as a string (e.g.: "1 == 1" or "$('#bar').is(':visible')" or +* as a callback function. +* @param onReady what to do when testFx condition is fulfilled, +* it can be passed in as a string (e.g.: "1 == 1" or "$('#bar').is(':visible')" or +* as a callback function. +* @param timeOutMillis the max amount of time to wait. If not specified, 3 sec is used. +*/ +function waitFor(testFx, onReady, timeOutMillis) { + var maxtimeOutMillis = timeOutMillis ? timeOutMillis : 3001, //< Default Max Timout is 3s + start = new Date().getTime(), + condition = false, + interval = setInterval(function() { + if ( (new Date().getTime() - start < maxtimeOutMillis) && !condition ) { + // If not time-out yet and condition not yet fulfilled + condition = (typeof(testFx) === "string" ? eval(testFx) : testFx()); //< defensive code + } else { + if(!condition) { + // If condition still not fulfilled (timeout but condition is 'false') + console.log("'waitFor()' timeout"); + phantom.exit(1); + } else { + // Condition fulfilled (timeout and/or condition is 'true') + typeof(onReady) === "string" ? eval(onReady) : onReady(); //< Do what it's supposed to do once the condition is fulfilled + clearInterval(interval); //< Stop this interval + } + } + }, 100); //< repeat check every 250ms +}; + + +if (phantom.args.length === 0 || phantom.args.length > 2) { + console.log('Usage: run-qunit.js URL'); + phantom.exit(); +} + +var page = new WebPage(); + +// Route "console.log()" calls from within the Page context to the main Phantom context (i.e. current "this") +page.onConsoleMessage = function(msg) { + console.log(msg); +}; + +page.open(phantom.args[0], function(status){ + if (status !== "success") { + console.log("Unable to access network"); + phantom.exit(); + } else { + waitFor(function(){ + return page.evaluate(function(){ + var el = document.getElementById('qunit-testresult'); + if (el && el.innerText.match('completed')) { + return true; + } + return false; + }); + }, function(){ + var failedNum = page.evaluate(function(){ + var el = document.getElementById('qunit-testresult'); + try { + return el.getElementsByClassName('failed')[0].innerHTML; + } catch (e) { } + return 10000; + }); + phantom.exit((parseInt(failedNum, 10) > 0) ? 1 : 0); + }); + } +}); \ No newline at end of file