├── .gitignore ├── tests ├── modules │ ├── a.js │ ├── b.js │ ├── c.js │ ├── d.js │ ├── has-plugin-test.js │ └── test-branch.js ├── base.gif ├── requirejs-plugin.html ├── report.php ├── results.php ├── runTests.html └── require.js ├── package.json ├── detect ├── strings.js ├── script.js ├── function.js ├── dates.js ├── json.js ├── __base.js ├── video.js ├── audio.js ├── graphics.js ├── array.js ├── events.js ├── object.js ├── form.js ├── css.js ├── features.js ├── dom.js └── bugs.js ├── _plugin.js ├── has.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | tests/results.data -------------------------------------------------------------------------------- /tests/modules/a.js: -------------------------------------------------------------------------------- 1 | define([], function(){ 2 | return "a"; 3 | }); -------------------------------------------------------------------------------- /tests/modules/b.js: -------------------------------------------------------------------------------- 1 | define([], function(){ 2 | return "b"; 3 | }); -------------------------------------------------------------------------------- /tests/modules/c.js: -------------------------------------------------------------------------------- 1 | define([], function(){ 2 | return "c"; 3 | }); -------------------------------------------------------------------------------- /tests/modules/d.js: -------------------------------------------------------------------------------- 1 | define([], function(){ 2 | return "d"; 3 | }); -------------------------------------------------------------------------------- /tests/base.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kriszyp/has.js/master/tests/base.gif -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "has", 3 | "directories": { 4 | "lib": "./detect" 5 | }, 6 | "main": "./has.js" 7 | } 8 | -------------------------------------------------------------------------------- /tests/modules/has-plugin-test.js: -------------------------------------------------------------------------------- 1 | define(["has"], function(has){ 2 | has.add("true", true); 3 | has.add("false", function(){ 4 | return false; 5 | }); 6 | return has; 7 | }); -------------------------------------------------------------------------------- /tests/modules/test-branch.js: -------------------------------------------------------------------------------- 1 | define(["has!dom?./a:./b", "has/array!array-every?./a:./b", "./has-plugin-test!true?false?./a:false?./b:./c:./d", "./has-plugin-test!false?a"], function(dom, every, branch, none){ 2 | console.assert(dom === "a","dom"); 3 | console.assert(every === ([].every ? "a": "b"), "every"); 4 | console.assert(branch === "c", "branch"); 5 | console.assert(none === undefined, "undefined"); 6 | console.log("passed tests"); 7 | }); -------------------------------------------------------------------------------- /detect/strings.js: -------------------------------------------------------------------------------- 1 | (function(define){ 2 | define(["has"], function(has){ 3 | 4 | var addtest = has.add; 5 | 6 | // String tests 7 | addtest("string-trim", function(){ 8 | return ({}).toString.call(''.trim) == "[object Function]"; 9 | }); 10 | 11 | return has; 12 | }); 13 | })(typeof define != "undefined" ? define : function(deps, factory){ 14 | factory(has); // use global has() if a module system is not available 15 | }); 16 | 17 | -------------------------------------------------------------------------------- /detect/script.js: -------------------------------------------------------------------------------- 1 | (function(define){ 2 | define(["has"], function(has){ 3 | 4 | var addtest = has.add; 5 | 6 | if(!has("dom")){ return; } 7 | 8 | var script = document.createElement("script"); 9 | 10 | addtest("script-defer", function(){ 11 | return ("defer" in script); 12 | }); 13 | 14 | addtest("script-async", function(){ 15 | return ("async" in script); 16 | }); 17 | return has; 18 | }); 19 | })(typeof define != "undefined" ? define : function(deps, factory){ 20 | factory(has); // use global has() if a module system is not available 21 | }); 22 | 23 | -------------------------------------------------------------------------------- /tests/requirejs-plugin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | has.js as RequireJS module and plugin 5 | 6 | 7 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /detect/function.js: -------------------------------------------------------------------------------- 1 | (function(define){ 2 | define(["has"], function(has){ 3 | 4 | var addtest = has.add, 5 | toString = {}.toString, 6 | FUNCTION_CLASS = "[object Function]"; 7 | 8 | // Function tests 9 | addtest("function-bind", function(){ 10 | return toString.call(Function.bind) == FUNCTION_CLASS; 11 | }); 12 | 13 | addtest("function-caller", function(){ 14 | function test(){ return test.caller !== undefined; } 15 | return test(); 16 | }); 17 | 18 | return has; 19 | }); 20 | })(typeof define != "undefined" ? define : function(deps, factory){ 21 | factory(has); // use global has() if a module system is not available 22 | }); 23 | 24 | -------------------------------------------------------------------------------- /tests/report.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | has.js submissions 5 | 6 | 7 | 8 |

FTW

9 | 10 | Data stashed. Really, Thanks!

"; 19 | }else{ 20 | print "

It appears there was an issue.

"; 21 | } 22 | }else{ 23 | print "

No data set sent along?

"; 24 | } 25 | 26 | ?> 27 |

back to runTests.html

28 | 29 | -------------------------------------------------------------------------------- /detect/dates.js: -------------------------------------------------------------------------------- 1 | (function(define){ 2 | define(["has"], function(has){ 3 | 4 | var toString = {}.toString, 5 | addtest = has.add, 6 | NEW_DATE = new Date, 7 | FUNCTION_CLASS = "[object Function]"; 8 | 9 | // Date tests 10 | addtest("date-toisostring", function(){ 11 | return toString.call(NEW_DATE.toISOString) == FUNCTION_CLASS; 12 | }); 13 | 14 | addtest("date-tojson", function(){ 15 | return toString.call(NEW_DATE.toJSON) == FUNCTION_CLASS; 16 | }); 17 | 18 | addtest("date-now", function(){ 19 | return toString.call(Date.now) == FUNCTION_CLASS; 20 | }); 21 | 22 | return has; 23 | }); 24 | })(typeof define != "undefined" ? define : function(deps, factory){ 25 | factory(has); // the use global has() if a module system is not available 26 | }); 27 | 28 | -------------------------------------------------------------------------------- /detect/json.js: -------------------------------------------------------------------------------- 1 | (function(define){ 2 | define(["has"], function(has){ 3 | 4 | var addtest = has.add, 5 | STR = "string", 6 | FN = "function" 7 | ; 8 | 9 | // JSON tests 10 | addtest("json-parse", function(g){ 11 | var parsed, supported = false; 12 | if("JSON" in g && typeof JSON.parse == FN){ 13 | parsed = JSON.parse('{"a":true}'); 14 | supported = !!(parsed && parsed.a); 15 | } 16 | return supported; 17 | }); 18 | 19 | addtest("json-stringify", function(g){ 20 | return ("JSON" in g) && typeof JSON.stringify == FN && JSON.stringify({a:true}) == '{"a":true}'; 21 | }); 22 | 23 | return has; 24 | }); 25 | })(typeof define != "undefined" ? define : function(deps, factory){ 26 | factory(has); // use global has() if a module system is not available 27 | }); 28 | -------------------------------------------------------------------------------- /detect/__base.js: -------------------------------------------------------------------------------- 1 | (function(define){ 2 | define(["has"], function(has){ 3 | 4 | var addtest = has.add, 5 | STR = "string", 6 | FN = "function" 7 | ; 8 | 9 | // above this line is "boilerplate" template for all test groupings. 10 | // while each module has it's own enclosing function, these strings 11 | // and aliases passed through the self-executing-anon-function are 12 | // common across all tests. This is so we can blindly pull parts 13 | // of other test modules into a single rollup. Tests should be 14 | // "as standalone as humanly possible", with some exceptions (and 15 | // then, only for the benefit of performance and sharing) 16 | 17 | // put your tests here, eg: 18 | addtest("has-test-skeleton", function(global, document, anElement){ 19 | return true; // Boolean 20 | }); 21 | return has; 22 | }); 23 | })(typeof define != "undefined" ? define : function(deps, factory){ 24 | factory(has); // use global has() if a module system is not available 25 | }); 26 | 27 | -------------------------------------------------------------------------------- /detect/video.js: -------------------------------------------------------------------------------- 1 | (function(define){ 2 | define(["has"], function(has){ 3 | 4 | var addtest = has.add; 5 | 6 | if(!has("dom")){ return; } 7 | 8 | var video = document.createElement("video"); 9 | 10 | addtest("video", function(){ 11 | return has.isHostType(video, "canPlayType"); 12 | }); 13 | 14 | // note: in FF 3.5.1 and 3.5.0 only, "no" was a return value instead of empty string. 15 | 16 | addtest("video-h264-baseline", function(){ 17 | // workaround required for ie9, who doesn't report video support without audio codec specified. 18 | // bug 599718 @ msft connect 19 | var h264 = 'video/mp4; codecs="avc1.42E01E'; 20 | return has("video") && (video.canPlayType(h264 + '"') || video.canPlayType(h264 + ', mp4a.40.2"')); 21 | }); 22 | 23 | addtest("video-ogg-theora", function(){ 24 | return has("video") && video.canPlayType('video/ogg; codecs="theora, vorbis"'); 25 | }); 26 | 27 | addtest("video-webm", function(){ 28 | return has("video") && video.canPlayType('video/webm; codecs="vp8, vorbis"'); 29 | }); 30 | 31 | return has; 32 | }); 33 | })(typeof define != "undefined" ? define : function(deps, factory){ 34 | factory(has); // use global has() if a module system is not available 35 | }); 36 | 37 | -------------------------------------------------------------------------------- /_plugin.js: -------------------------------------------------------------------------------- 1 | define(["require"], function(require){ 2 | // this allows us to use has as a dependency plugin for RequireJS and other AMD loaders (like Dojo). The syntax supports the ternary operation for branching: 3 | // define("my-module", ["has!array-every?module-using-every:module-using-for-loop"], function(arrayModule){ 4 | // }); 5 | var has; 6 | return function(id, parentRequire, loaded, config){ 7 | // split into the different branches, based on has features 8 | // first parse the id 9 | var tokens = id.match(/[\?:]|[^:\?]*/g), i = 0; 10 | has = has || config.isBuild ? 11 | function(feature){ 12 | return config[feature]; 13 | } : 14 | require("has"); 15 | function get(skip){ 16 | var operator, term = tokens[i++]; 17 | if(term == ":"){ 18 | // empty string module name, resolves to undefined 19 | return; 20 | }else{ 21 | // postfixed with a ? means it is a feature to branch on, the term is the name of the feature 22 | if(tokens[i++] == "?"){ 23 | if(!skip && has(term)){ 24 | // matched the feature, get the first value from the options 25 | return get(); 26 | }else{ 27 | // did not match, get the second value, passing over the first 28 | get(true); 29 | return get(skip); 30 | } 31 | } 32 | // a module 33 | return term; 34 | } 35 | } 36 | id = get(); 37 | if(id){ 38 | parentRequire([id], loaded); 39 | }else{ 40 | loaded(); 41 | } 42 | }; 43 | }); -------------------------------------------------------------------------------- /detect/audio.js: -------------------------------------------------------------------------------- 1 | (function(define){ 2 | define(["has"], function(has){ 3 | 4 | var CAN_PLAY_GUESSES = { "maybe": 1, "probably": 1 }, 5 | addtest = has.add, 6 | STR = "string", 7 | FN = "function" 8 | ; 9 | 10 | if(!has("dom")){ return; } 11 | 12 | var audio = document.createElement("audio"); 13 | 14 | addtest("audio", function(){ 15 | return has.isHostType(audio, "canPlayType"); 16 | }); 17 | 18 | // TODO: evaluate if these tests fit within the has.js scope because they don't 19 | // provide a definate yes or no answer 20 | // 21 | // NOTE: Opera returns a false-negative in this test if there are single spaces 22 | // around the codes value, e.g. codes='vorbis' 23 | addtest("audio-ogg", function(){ 24 | return has("audio") && !!CAN_PLAY_GUESSES[audio.canPlayType("audio/ogg; codecs=vorbis")]; 25 | }); 26 | 27 | addtest("audio-mp3", function(){ 28 | return has("audio") && !!CAN_PLAY_GUESSES[audio.canPlayType("audio/mpeg;")]; 29 | }); 30 | 31 | addtest("audio-wav", function(){ 32 | return has("audio") && !!CAN_PLAY_GUESSES[audio.canPlayType("audio/wav; codecs=1")]; 33 | }); 34 | 35 | addtest("audio-m4a", function(){ 36 | return has("audio") && !!(CAN_PLAY_GUESSES[audio.canPlayType("audio/x-m4a;")] || 37 | CAN_PLAY_GUESSES[audio.canPlayType("audio/aac;")]); 38 | }); 39 | return has; 40 | 41 | }); 42 | })(typeof define != "undefined" ? define : function(deps, factory){ 43 | factory(has); // the use global has() if a module system is not available 44 | }); 45 | -------------------------------------------------------------------------------- /detect/graphics.js: -------------------------------------------------------------------------------- 1 | (function(define){ 2 | define(["has", "./dom"], function(has){ 3 | 4 | var addtest = has.add, 5 | FN = "function", 6 | toString = {}.toString, 7 | STR = "string", 8 | FN = "function" 9 | ; 10 | 11 | if(!has("dom")){ return; } 12 | 13 | addtest("canvas", function(g){ 14 | return has.isHostType(g, "CanvasRenderingContext2D"); 15 | }); 16 | 17 | addtest("canvas-webgl", function(g){ 18 | return has.isHostType(g, "WebGLRenderingContext"); 19 | }); 20 | 21 | addtest("canvas-text", function(g, d){ 22 | return has("canvas") && typeof d.createElement("canvas").getContext("2d").fillText == FN; 23 | }); 24 | 25 | 26 | var svgNS = "http://www.w3.org/2000/svg"; 27 | 28 | addtest("svg", function(g){ 29 | return ("SVGAngle" in g); 30 | }); 31 | 32 | addtest("svg-inlinesvg", function(g, d, el){ 33 | var supported = null; 34 | el.innerHTML = ""; 35 | supported = (el.firstChild && el.firstChild.namespaceURI) == svgNS; 36 | el.innerHTML = ""; 37 | return supported; 38 | }); 39 | 40 | addtest("svg-smil", function(g, d){ 41 | return has("dom-createelementns") && /SVG/.test(toString.call(d.createElementNS(svgNS, "animate"))); 42 | }); 43 | 44 | addtest("svg-clippaths", function(g, d){ 45 | return has("dom-createelementns") && /SVG/.test(toString.call(d.createElementNS(svgNS, "clipPath"))); 46 | }); 47 | 48 | addtest("vml", function(g, d, el){ 49 | /* 50 | Sources: 51 | http://en.wikipedia.org/wiki/Vector_Markup_Language 52 | http://msdn.microsoft.com/en-us/library/bb263897(v=VS.85).aspx 53 | http://www.svg-vml.net/Zibool-compar.htm 54 | */ 55 | el.innerHTML = ""; 56 | var supported = ("adj" in el.firstChild); 57 | el.innerHTML = ""; 58 | return supported; 59 | }); 60 | 61 | return has; 62 | }); 63 | })(typeof define != "undefined" ? define : function(deps, factory){ 64 | factory(has); // use global has() if a module system is not available 65 | }); 66 | 67 | -------------------------------------------------------------------------------- /detect/array.js: -------------------------------------------------------------------------------- 1 | (function(define){ 2 | define(["has"], function(has){ 3 | var toString = {}.toString, 4 | addtest = has.add, 5 | EMPTY_ARRAY = [], 6 | FUNCTION_CLASS = "[object Function]"; 7 | 8 | // Array tests 9 | addtest("array-every", function(){ 10 | return toString.call(EMPTY_ARRAY.every) == FUNCTION_CLASS; 11 | }); 12 | 13 | addtest("array-filter", function(){ 14 | return toString.call(EMPTY_ARRAY.filter) == FUNCTION_CLASS; 15 | }); 16 | 17 | addtest("array-foreach", function(){ 18 | return toString.call(EMPTY_ARRAY.forEach) == FUNCTION_CLASS; 19 | }); 20 | 21 | addtest("array-indexof", function(){ 22 | return toString.call(EMPTY_ARRAY.indexOf) == FUNCTION_CLASS; 23 | }); 24 | 25 | addtest("array-isarray", function(){ 26 | return toString.call(Array.isArray) == FUNCTION_CLASS && 27 | Array.isArray(EMPTY_ARRAY) === true; 28 | }); 29 | 30 | addtest("array-lastindexof", function(){ 31 | return toString.call(EMPTY_ARRAY.lastIndexOf) == FUNCTION_CLASS; 32 | }); 33 | 34 | addtest("array-map", function(){ 35 | return toString.call(EMPTY_ARRAY.map) == FUNCTION_CLASS; 36 | }); 37 | 38 | addtest("array-reduce", function(){ 39 | return toString.call(EMPTY_ARRAY.reduce) == FUNCTION_CLASS; 40 | }); 41 | 42 | addtest("array-reduceright", function(){ 43 | return toString.call(EMPTY_ARRAY.reduce) == FUNCTION_CLASS; 44 | }); 45 | 46 | addtest("array-some", function(){ 47 | return toString.call(EMPTY_ARRAY.some) == FUNCTION_CLASS; 48 | }); 49 | 50 | addtest("array-es5", function(){ 51 | return has("array-every") && has("array-filter") && has("array-foreach") && 52 | has("array-indexof") && has("array-isarray") && has("array-lastindexof") && 53 | has("array-map") && has("array-reduce") && has("array-reduceright") && 54 | has("array-some"); 55 | }); 56 | 57 | addtest("array-slice-nodelist", function(g, d, el){ 58 | var supported = false, 59 | de = d.documentElement, 60 | id = de.id; 61 | 62 | // Opera 9.25 bug 63 | de.id = "length"; 64 | // older Safari will return an empty array 65 | try{ 66 | supported = !!EMPTY_ARRAY.slice.call(d.childNodes, 0)[0]; 67 | }catch(e){} 68 | 69 | de.id = id; 70 | return supported; 71 | }); 72 | return has; 73 | }); 74 | })(typeof define != "undefined" ? define : function(deps, factory){ 75 | factory(has); // the use global has() if a module system is not available 76 | }); 77 | 78 | -------------------------------------------------------------------------------- /detect/events.js: -------------------------------------------------------------------------------- 1 | (function(define){ 2 | define(["has"], function(has){ 3 | var addtest = has.add; 4 | 5 | function event_tests(g, d, test){ 6 | var de = d.documentElement, 7 | input = d.createElement("input"), 8 | result = { 9 | metakey: null, 10 | preventdefault: null, 11 | stoppropagation: null, 12 | srcelement: null, 13 | relatedtarget: null 14 | }; 15 | 16 | if(has.isHostType(input, "click")){ 17 | input.type = "checkbox"; 18 | input.style.display = "none"; 19 | input.onclick = function(e){ 20 | e || (e = g.event); 21 | result.metakey = ("metaKey" in e); 22 | result.stoppropagation = ("stopPropagation" in e); 23 | result.preventdefault = ("preventDefault" in e); 24 | result.srcelement = ("srcElement" in e); 25 | result.relatedtarget = ("relatedTarget" in e); 26 | }; 27 | try{ 28 | de.insertBefore(input, de.firstChild); 29 | input.click(); 30 | de.removeChild(input); 31 | }catch(e){} 32 | input.onclick = null; 33 | } 34 | 35 | addtest("event-metakey", result.metakey); 36 | addtest("event-preventdefault", result.preventdefault); 37 | addtest("event-stoppropagation", result.stoppropagation); 38 | addtest("event-srcelement", result.srcelement); 39 | addtest("event-relatedtarget", result.relatedtarget); 40 | return result[test]; 41 | } 42 | 43 | if(!has("dom")){ return; } 44 | 45 | addtest("event-contextmenu", function(g, d, el){ 46 | var supported = null; 47 | if(has.isHostType(el, "setAttribute")){ 48 | el.setAttribute("oncontextmenu", ""); 49 | supported = (typeof el.oncontextmenu != "undefined"); 50 | } 51 | return supported; 52 | }); 53 | 54 | addtest("event-metakey", function(g, d){ 55 | return event_tests(g, d, "metakey"); 56 | }); 57 | 58 | addtest("event-preventdefault", function(g, d){ 59 | return event_tests(g, d, "preventdefault"); 60 | }); 61 | 62 | addtest("event-stoppropagation", function(g, d){ 63 | return event_tests(g, d, "stoppropagation"); 64 | }); 65 | 66 | addtest("event-srcelement", function(g, d){ 67 | return event_tests(g, d, "srcelement"); 68 | }); 69 | 70 | addtest("event-relatedtarget", function(g, d){ 71 | return event_tests(g, d, "relatedtarget"); 72 | }); 73 | 74 | return has; 75 | }); 76 | })(typeof define != "undefined" ? define : function(deps, factory){ 77 | factory(has); // use global has() if a module system is not available 78 | }); 79 | 80 | -------------------------------------------------------------------------------- /tests/results.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 24 | 25 | has.js results matrix 26 | 27 | 28 | array( 36 | // $test1, $test2, $test3 // where each are results of a test from this UA 37 | // ) 38 | // ) 39 | $out = array(); 40 | foreach($lines as $line){ 41 | $data = json_decode($line); 42 | $ua = $data -> useragent; 43 | if(empty($out[$ua])){ 44 | $out[$ua] = array(); 45 | } 46 | unset($data -> useragent); 47 | $out[$ua][] = $data; 48 | } 49 | 50 | // go over each useragent, and compare each of the tests in the dataset. 51 | // if they are all the same, display without issue. 52 | 53 | global $master_test_list; 54 | $master_test_list = array(); 55 | 56 | foreach($out as $agent => $dataset){ 57 | 58 | $numtests = count($dataset); 59 | 60 | // pull out all the unique test names we know about 61 | foreach($dataset as $datapart){ 62 | foreach($datapart as $name => $value){ 63 | if(!in_array($name, $master_test_list)){ 64 | $master_test_list[] = $name; 65 | } 66 | } 67 | } 68 | 69 | } 70 | 71 | function mixdown($results){ 72 | // this is making me insane. trying to cover the cases where a couple 73 | // things may or may not happen. 74 | // * some test may not have been written at the time the other data was collected 75 | // + in this case we need to specify "unknown" class. 76 | // * some tests, sorted by useragent, may have varying results. 77 | // + in this case, we need to use 'maybe' class ...... 78 | // + this is probably bad, as it means perhaps faulty data has 79 | // gotten into our suite. 80 | // jesus christ i should just flatten the tests immediately. huh? 81 | $ret = array(); 82 | foreach($results as $test){ 83 | foreach($test as $k => $v){ 84 | if($v == "true"){ 85 | // print $v . ":"; 86 | } 87 | //print $k . "
"; 88 | } 89 | } 90 | } 91 | 92 | ?> 93 | 94 |

Thanks!

95 | 96 | 97 | -------------------------------------------------------------------------------- /detect/object.js: -------------------------------------------------------------------------------- 1 | (function(define){ 2 | define(["has"], function(has){ 3 | 4 | // FIXME: break this out into "modules", like array.js, dom.js, lang.js (?) ^ph 5 | 6 | // we could define a couple "constants" for reuse ... 7 | // just need to ensure they are the same across detect/*.js 8 | // so we can wrap in a single (fn(){})() at 'build' ^ph 9 | var addtest = has.add, 10 | toString = {}.toString, 11 | FN = "function", 12 | FUNCTION_CLASS = "[object Function]", 13 | OBJECT = Object 14 | ; 15 | 16 | // true for Gecko, Webkit, Opera 10.5+ 17 | addtest("object-__proto__", function(){ 18 | var supported = false, 19 | arr = [], 20 | obj = { }, 21 | backup = arr.__proto__; 22 | 23 | if(arr.__proto__ == Array.prototype && 24 | obj.__proto__ == Object.prototype){ 25 | // test if it's writable and restorable 26 | arr.__proto__ = obj; 27 | supported = typeof arr.push == "undefined"; 28 | arr.__proto__ = backup; 29 | } 30 | return supported && typeof arr.push == FN; 31 | }); 32 | 33 | addtest("object-create", function(){ 34 | return toString.call(OBJECT.create) == FUNCTION_CLASS; 35 | }); 36 | 37 | addtest("object-getprototypeof", function(){ 38 | return toString.call(OBJECT.getPrototypeOf) == FUNCTION_CLASS; 39 | }); 40 | 41 | addtest("object-seal", function(){ 42 | return toString.call(OBJECT.seal) == FUNCTION_CLASS; 43 | }); 44 | 45 | addtest("object-freeze", function(){ 46 | return toString.call(OBJECT.freeze) == FUNCTION_CLASS; 47 | }); 48 | 49 | addtest("object-issealed", function(){ 50 | return toString.call(OBJECT.isSealed) == FUNCTION_CLASS; 51 | }); 52 | 53 | addtest("object-isfrozen", function(){ 54 | return toString.call(OBJECT.isFrozen) == FUNCTION_CLASS; 55 | }); 56 | 57 | addtest("object-keys", function(){ 58 | return toString.call(OBJECT.keys) == FUNCTION_CLASS; 59 | }); 60 | 61 | addtest("object-preventextensions", function(){ 62 | return toString.call(OBJECT.preventExtensions) == FUNCTION_CLASS; 63 | }); 64 | 65 | addtest("object-isextensible", function(){ 66 | return toString.call(OBJECT.isExtensible) == FUNCTION_CLASS; 67 | }); 68 | 69 | addtest("object-defineproperty", function(){ 70 | return toString.call(OBJECT.defineProperty) == FUNCTION_CLASS; 71 | }); 72 | 73 | addtest("object-defineproperties", function(){ 74 | return toString.call(OBJECT.defineProperties) == FUNCTION_CLASS; 75 | }); 76 | 77 | addtest("object-getownpropertydescriptor", function(){ 78 | return toString.call(OBJECT.getOwnPropertyDescriptor) == FUNCTION_CLASS; 79 | }); 80 | 81 | addtest("object-getownpropertynames", function(){ 82 | return toString.call(OBJECT.getOwnPropertyNames) == FUNCTION_CLASS; 83 | }); 84 | 85 | addtest("object-es5", function(){ 86 | return has("object-create") && has("object-defineproperties") && has("object-defineproperty") && 87 | has("object-freeze") && has("object-getownpropertydescriptor") && has("object-getownpropertynames") && 88 | has("object-getprototypeof") && has("object-isextensible") && has("object-isfrozen") && 89 | has("object-issealed") && has("object-keys") && has("object-preventextensions") && has("object-seal"); 90 | }); 91 | 92 | return has; 93 | }); 94 | })(typeof define != "undefined" ? define : function(deps, factory){ 95 | factory(has); // use global has() if a module system is not available 96 | }); 97 | 98 | -------------------------------------------------------------------------------- /detect/form.js: -------------------------------------------------------------------------------- 1 | (function(define){ 2 | define(["has"], function(has){ 3 | 4 | var STR = "string", 5 | addtest = has.add, 6 | FN = "function" 7 | ; 8 | 9 | function typeValidates( type ){ 10 | input.setAttribute("type", type); 11 | input.value = "\x01"; 12 | return has("input-checkvalidity") && input.type == type && 13 | (/search|tel/.test(type) || input.value != "\x01" || !input.checkValidity()); 14 | } 15 | 16 | if(!has("dom")){ return; } 17 | 18 | var input = document.createElement("input"); 19 | 20 | addtest("input-checkvalidity", function(){ 21 | return has.isHostType(input, "checkValidity"); 22 | }); 23 | 24 | addtest("input-attr-autocomplete", function(){ 25 | return ("autocomplete" in input); 26 | }); 27 | 28 | addtest("input-attr-autofocus", function(){ 29 | return ("autofocus" in input); 30 | }); 31 | 32 | addtest("input-attr-list", function(){ 33 | return ("list" in input); 34 | }); 35 | 36 | addtest("input-attr-placeholder", function(){ 37 | return ("placeholder" in input); 38 | }); 39 | 40 | addtest("input-attr-max", function(){ 41 | return ("max" in input); 42 | }); 43 | 44 | addtest("input-attr-maxlength", function(){ 45 | return ("maxLength" in input); 46 | }); 47 | 48 | addtest("input-attr-min", function(){ 49 | return ("min" in input); 50 | }); 51 | 52 | addtest("input-attr-multiple", function(){ 53 | return ("multiple" in input); 54 | }); 55 | 56 | addtest("input-attr-pattern", function(){ 57 | return ("pattern" in input); 58 | }); 59 | 60 | addtest("input-attr-readonly", function(){ 61 | return ("readOnly" in input); 62 | }); 63 | 64 | addtest("input-attr-required", function(){ 65 | return ("required" in input); 66 | }); 67 | 68 | addtest("input-attr-size", function(){ 69 | return ("size" in input); 70 | }); 71 | 72 | addtest("input-attr-step", function(){ 73 | return ("step" in input); 74 | }); 75 | 76 | addtest("input-attr-selectedoption", function(){ 77 | return ("selectedOption" in input); 78 | }); 79 | 80 | addtest("input-attr-indeterminate ", function(){ 81 | return ("indeterminate " in input); 82 | }); 83 | 84 | addtest("input-attr-willvalidate", function(){ 85 | return ("willValidate" in input); 86 | }); 87 | 88 | addtest("input-attr-valueasnumber", function(){ 89 | return ("valueAsNumber" in input); 90 | }); 91 | 92 | addtest("input-attr-valueasdate", function(){ 93 | return ("valueAsDate" in input); 94 | }); 95 | 96 | addtest("input-attr-validity", function(){ 97 | return has.isHostType(input, "validity"); 98 | }); 99 | 100 | addtest("input-attr-validationmessage", function(){ 101 | return ("validationMessage" in input); 102 | }); 103 | 104 | addtest("input-attr-willvalidate", function(){ 105 | return ("willValidate" in input); 106 | }); 107 | 108 | addtest("input-type-color", function(){ 109 | return typeValidates("color"); 110 | }); 111 | 112 | addtest("input-type-search", function(){ 113 | return typeValidates("search"); 114 | }); 115 | 116 | addtest("input-type-tel", function(){ 117 | return typeValidates("tel"); 118 | }); 119 | 120 | addtest("input-type-url", function(){ 121 | return typeValidates("url"); 122 | }); 123 | 124 | addtest("input-type-email", function(){ 125 | return typeValidates("email"); 126 | }); 127 | 128 | addtest("input-type-datetime", function(){ 129 | return typeValidates("datetime"); 130 | }); 131 | 132 | addtest("input-type-date", function(){ 133 | return typeValidates("date"); 134 | }); 135 | 136 | addtest("input-type-month", function(){ 137 | return typeValidates("month"); 138 | }); 139 | 140 | addtest("input-type-week", function(){ 141 | return typeValidates("week"); 142 | }); 143 | 144 | addtest("input-type-time", function(){ 145 | return typeValidates("time"); 146 | }); 147 | 148 | addtest("input-type-datetime-local", function(){ 149 | return typeValidates("datetime-local"); 150 | }); 151 | 152 | addtest("input-type-number", function(){ 153 | return typeValidates("number"); 154 | }); 155 | 156 | addtest("input-type-range", function(g, d){ 157 | return typeValidates("range"); 158 | }); 159 | 160 | return has; 161 | }); 162 | })(typeof define != "undefined" ? define : function(deps, factory){ 163 | factory(has); // use global has() if a module system is not available 164 | }); 165 | 166 | -------------------------------------------------------------------------------- /detect/css.js: -------------------------------------------------------------------------------- 1 | (function(define){ 2 | define(["has"], function(has){ 3 | 4 | if(!has("dom")){ return; } 5 | var addtest = has.add, 6 | cssprop = has.cssprop; 7 | // FROM cft.js 8 | addtest("css-enabled", function(g, d, el){ 9 | var supported, fake, 10 | de = d.documentElement, 11 | root = d.body || (function(){ 12 | fake = true; 13 | return de.insertBefore(d.createElement("body"), de.firstChild); 14 | }()); 15 | 16 | el.style.display = "none"; 17 | root.insertBefore(el, root.firstChild); 18 | supported = (el.offsetWidth === 0); 19 | root.removeChild(el); 20 | 21 | if(fake){ 22 | root.parentNode.removeChild(root); 23 | } 24 | return supported; 25 | }); 26 | 27 | addtest("css-content-box", function(g, d, el){ 28 | var fake, root, 29 | de = d.documentElement, 30 | supported = null; 31 | 32 | if(has("css-enabled")){ 33 | root = d.body || (function(){ 34 | fake = true; 35 | return de.insertBefore(d.createElement("body"), de.firstChild); 36 | }()); 37 | 38 | el.style.cssText = "position: absolute; top: -4000px; width: 40px; height: 40px; border: 1px solid black;"; 39 | root.insertBefore(el, root.firstChild); 40 | 41 | supported = (el.clientWidth == 40); 42 | root.removeChild(el); 43 | el.style.cssText = ""; 44 | } 45 | if(fake){ 46 | root.parentNode.removeChild(root); 47 | } 48 | return supported; 49 | }); 50 | 51 | addtest("css-position-fixed", function(g, d, el){ 52 | var backup, control, fake, root, 53 | de = d.documentElement, 54 | supported = null; 55 | 56 | if(has("css-enabled")){ 57 | control = el.cloneNode(false); 58 | root = d.body || (function(){ 59 | fake = true; 60 | return de.insertBefore(d.createElement("body"), de.firstChild); 61 | }()); 62 | 63 | backup = root.style.cssText; 64 | root.style.cssText = "padding:0;margin:0"; 65 | el.style.cssText = "position:fixed;top:42px"; 66 | 67 | root.insertBefore(control, root.firstChild); 68 | root.insertBefore(el, control); 69 | supported = (el.offsetTop !== control.offsetTop); 70 | 71 | root.removeChild(el); 72 | root.removeChild(control); 73 | root.style.cssText = backup; 74 | el.style.cssText = ""; 75 | } 76 | if(fake){ 77 | root.parentNode.removeChild(root); 78 | } 79 | return supported; 80 | }); 81 | 82 | // FROM cft.js 83 | addtest("css-rgba", function(g, d, el){ 84 | var re = /^rgba/, 85 | supported = null; 86 | 87 | if(has("css-enabled")){ 88 | try{ 89 | el.style.color = "rgba(1,1,1,0.5)"; 90 | supported = re.test(el.style.color); 91 | el.style.color = ""; 92 | }catch(e){} 93 | } 94 | return supported; 95 | }); 96 | 97 | addtest("css-border-radius", function(g, d, el){ 98 | return cssprop("borderRadius", el); 99 | }); 100 | 101 | addtest("css-box-shadow", function(g, d, el){ 102 | return cssprop("boxShadow", el); 103 | }); 104 | 105 | addtest("css-box-sizing", function(g, d, el){ 106 | return cssprop("boxSizing", el); 107 | }); 108 | 109 | addtest("css-opacity", function(g, d, el){ 110 | return cssprop("opacity", el); 111 | }); 112 | 113 | addtest("css-opacity-filter", function(g, d){ 114 | return !has("css-opacity") && (typeof d.documentElement.filters != "undefined"); 115 | }); 116 | 117 | addtest("css-resize", function(g, d, el){ 118 | return cssprop("resize", el); 119 | }); 120 | 121 | addtest("css-selectable", function(g, d, el){ 122 | return cssprop("userSelect", el); 123 | }); 124 | 125 | addtest("css-style-float", function(g, d, el){ 126 | return cssprop("styleFloat", el); 127 | }); 128 | 129 | // TODO: Fix false positive in Opera 130 | addtest("css-pointerevents", function(g, d, el){ 131 | return cssprop("pointerEvents", el); 132 | }); 133 | 134 | addtest("css-text-overflow", function(g, d, el){ 135 | return cssprop("textOverflow", el); 136 | }); 137 | 138 | addtest("css-text-shadow", function(g, d, el){ 139 | return cssprop("textShadow", el); 140 | }); 141 | 142 | addtest("css-transform", function(g, d, el){ 143 | return cssprop("transform", el); 144 | }); 145 | 146 | // FIXME: modernizr has flexbox, backgroundsize, borderimage, cssanimations, csscolumns, cssgradients, 147 | // cssreflections, csstransforms, csstransforms3d, csstransitions, fontface 148 | 149 | return has; 150 | }); 151 | })(typeof define != "undefined" ? define : function(deps, factory){ 152 | factory(has); // the use global has() if a module system is not available 153 | }); 154 | 155 | -------------------------------------------------------------------------------- /detect/features.js: -------------------------------------------------------------------------------- 1 | (function(define){ 2 | define(["has"], function(has){ 3 | 4 | var STR = "string", 5 | addtest = has.add, 6 | FN = "function" 7 | ; 8 | 9 | // FIXME: isn't really native 10 | // miller device gives "[object Console]" in Opera & Webkit. Object in FF, though. ^pi 11 | addtest("native-console", function(g){ 12 | return ("console" in g); 13 | }); 14 | 15 | addtest("native-xhr", function(g){ 16 | return has.isHostType(g, "XMLHttpRequest"); 17 | }); 18 | 19 | addtest("native-cors-xhr", function(g){ 20 | return has("native-xhr") && ("withCredentials" in new XMLHttpRequest); 21 | }); 22 | 23 | addtest("native-xhr-uploadevents", function(g){ 24 | return has("native-xhr") && ("upload" in new XMLHttpRequest); 25 | }); 26 | 27 | addtest("activex", function(g){ 28 | return has.isHostType(g, "ActiveXObject"); 29 | }); 30 | 31 | addtest("activex-enabled", function(g){ 32 | var supported = null; 33 | if(has("activex")){ 34 | try{ 35 | supported = !!new ActiveXObject("htmlfile"); 36 | }catch(e){ 37 | supported = false; 38 | } 39 | } 40 | return supported; 41 | }); 42 | 43 | addtest("native-navigator", function(g){ 44 | return ("navigator" in g); 45 | }); 46 | 47 | /** 48 | * geolocation tests for the new Geolocation API specification. 49 | * This test is a standards compliant-only test; for more complete 50 | * testing, including a Google Gears fallback, please see: 51 | * http://code.google.com/p/geo-location-javascript/ 52 | * or view a fallback solution using google's geo API: 53 | * http://gist.github.com/366184 54 | */ 55 | 56 | addtest("native-geolocation", function(g){ 57 | return has("native-navigator") && ("geolocation" in g.navigator); 58 | }); 59 | 60 | addtest("native-crosswindowmessaging", function(g){ 61 | return ("postMessage" in g); 62 | }); 63 | 64 | addtest("native-orientation",function(g){ 65 | return ("ondeviceorientation" in g); 66 | }); 67 | 68 | /* 69 | * not sure if there is any point in testing for worker support 70 | * as an adequate fallback is impossible/pointless 71 | * 72 | * ^rw 73 | */ 74 | 75 | addtest("native-worker", function(g){ 76 | return ("Worker" in g); 77 | }); 78 | 79 | addtest("native-sharedworker", function(g){ 80 | return ("SharedWorker" in g); 81 | }); 82 | 83 | addtest("native-eventsource", function(g){ 84 | return ("EventSource" in g); 85 | }); 86 | 87 | // non-browser specific 88 | addtest("eval-global-scope", function(g){ 89 | var fnId = "__eval" + Number(new Date()), 90 | supported = false; 91 | 92 | // catch indirect eval call errors (i.e. in such clients as Blackberry 9530) 93 | try{ 94 | g.eval("var " + fnId + "=true"); 95 | }catch(e){} 96 | 97 | supported = (g[fnId] === true); 98 | if(supported){ 99 | try{ 100 | delete g[fnId]; 101 | }catch(e){ 102 | g[fnId] = undefined; 103 | } 104 | } 105 | return supported; 106 | }); 107 | 108 | // in chrome incognito mode, openDatabase is truthy, but using it 109 | // will throw an exception: http://crbug.com/42380 110 | // we create a dummy database. there is no way to delete it afterwards. sorry. 111 | addtest("native-sql-db", function(g){ 112 | var dbname = "hasjstestdb", 113 | supported = ("openDatabase" in g); 114 | 115 | if(supported){ 116 | try{ 117 | supported = !!openDatabase( dbname, "1.0", dbname, 2e4); 118 | }catch(e){ 119 | supported = false; 120 | } 121 | } 122 | return supported; 123 | }); 124 | 125 | // FIXME: hosttype 126 | // FIXME: moz and webkit now ship this prefixed. check all possible prefixes. ^pi 127 | addtest("native-indexeddb", function(g){ 128 | return ("indexedDB" in g); 129 | }); 130 | 131 | 132 | addtest("native-localstorage", function(g){ 133 | // Thanks Modernizr! 134 | var supported = false; 135 | try{ 136 | supported = ("localStorage" in g) && ("setItem" in localStorage); 137 | }catch(e){} 138 | return supported; 139 | }); 140 | 141 | addtest("native-sessionstorage", function(g){ 142 | // Thanks Modernizr! 143 | var supported = false; 144 | try{ 145 | supported = ("sessionStorage" in g) && ("setItem" in sessionStorage); 146 | }catch(e){} 147 | return supported; 148 | }); 149 | 150 | addtest("native-history-state", function(g){ 151 | return ("history" in g) && ("pushState" in history); 152 | }); 153 | 154 | addtest("native-websockets", function(g){ 155 | return ("WebSocket" in g); 156 | }); 157 | 158 | return has; 159 | }); 160 | })(typeof define != "undefined" ? define : function(deps, factory){ 161 | factory(has); // use global has() if a module system is not available 162 | }); 163 | 164 | -------------------------------------------------------------------------------- /detect/dom.js: -------------------------------------------------------------------------------- 1 | (function(define){ 2 | define(["has"], function(has){ 3 | 4 | var addtest = has.add; 5 | 6 | if(!has("dom")){ return; } 7 | 8 | addtest("dom-quirks", function(g, d, el){ 9 | var supported; 10 | if(typeof d.compatMode == "string"){ 11 | supported = (d.compatMode == "BackCompat"); 12 | }else{ 13 | el.style.width = "1"; 14 | supported = (e.style.width == "1px"); 15 | el.style.cssText = ""; 16 | } 17 | return supported; 18 | }); 19 | 20 | addtest("dom-dataset", function(g, d, el){ 21 | el.setAttribute("data-a-b", "c"); 22 | return has.isHostType(el, "dataset") && el.dataset.aB == "c"; 23 | }); 24 | 25 | // works in all but IE < 9 26 | addtest("dom-addeventlistener", function(g, d){ 27 | return has.isHostType(d, "addEventListener"); 28 | }); 29 | 30 | // works in all but IE 31 | addtest("dom-createelementns", function(g, d){ 32 | return has.isHostType(d, "createElementNS"); 33 | }); 34 | 35 | // should fail in webkit, as they dont support it. 36 | addtest("dom-attrmodified", function(g, d, el){ 37 | var supported = false, 38 | listener = function(){ supported = true; }; 39 | 40 | if(has("dom-addeventlistener")){ 41 | supported = false; 42 | el.addEventListener("DOMAttrModified", listener, false); 43 | el.setAttribute("___TEST___", true); 44 | el.removeAttribute("___TEST___", true); 45 | el.removeEventListener("DOMAttrModified", listener, false); 46 | } 47 | return supported; 48 | }); 49 | 50 | addtest("dom-subtreemodified", function(g, d, el){ 51 | var supported = false, 52 | listener = function(){ supported = true; }; 53 | 54 | if(has("dom-addeventlistener")){ 55 | supported = false; 56 | el.appendChild(d.createElement("div")); 57 | el.addEventListener("DOMSubtreeModified", listener, false); 58 | has.clearElement(el); 59 | el.removeEventListener("DOMSubtreeModified", listener, false); 60 | } 61 | return supported; 62 | }); 63 | 64 | // FROM cft.js 65 | addtest("dom-children", function(g, d, el){ 66 | var supported = false; 67 | if(has.isHostType(el, "children")){ 68 | var div = el.appendChild(d.createElement("div")), 69 | children = el.children; 70 | 71 | // Safari 2.x returns ALL children including text nodes 72 | el.appendChild(d.createTextNode("x")); 73 | div.appendChild(div.cloneNode(false)); 74 | supported = !!children && children.length == 1 && children[0] == div; 75 | has.clearElement(el); 76 | } 77 | return supported; 78 | }); 79 | 80 | // true for html, xhtml and unknown elements are case 81 | // sensitive to how they are written in the markup 82 | addtest("dom-tagname-uppercase", function(g, d, el){ 83 | return el.nodeName == "DIV"; 84 | }); 85 | 86 | addtest("dom-html5-elements", function(g, d, el){ 87 | el.innerHTML = ""; 88 | return el.childNodes.length == 1; 89 | }); 90 | 91 | // true for IE < 9 92 | // http://msdn.microsoft.com/en-us/library/ms536389(VS.85).aspx vs 93 | // http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-2141741547 94 | addtest("dom-create-attr", function(g, d){ 95 | var input, supported = false; 96 | try{ 97 | input = d.createElement(""); 98 | supported = input.type == "hidden" && input.name == "hasjs"; 99 | }catch(e){} 100 | return supported; 101 | }); 102 | 103 | // TODO: this test is really testing if expando's become attributes (IE) 104 | // true for IE 105 | addtest("dom-selectable", function(g, d, el){ 106 | var supported = false; 107 | try{ 108 | el.unselectable = "on"; 109 | supported = typeof el.attributes.unselectable != "undefined" && 110 | el.attributes.unselectable.value == "on"; 111 | el.unselectable = "off"; 112 | }catch(e){} 113 | return supported; 114 | }); 115 | 116 | // true for all modern browsers, including IE 9+ 117 | addtest("dom-computed-style", function(g, d){ 118 | return has.isHostType(d, "defaultView") && has.isHostType(d.defaultView, "getComputedStyle"); 119 | }); 120 | 121 | // true for IE 122 | addtest("dom-current-style", function(g, d){ 123 | return !has("dom-computed-style") && has.isHostType(d.documentElement, "currentStyle"); 124 | }); 125 | 126 | // true for IE 127 | addtest("dom-element-do-scroll", function(g, d){ 128 | return has.isHostType(d.documentElement, "doScroll"); 129 | }); 130 | 131 | // test for dynamic-updating base tag support (allows us to avoid href & src attr rewriting) 132 | // false for Firefox < 4 and IE < 8 133 | addtest("dom-dynamic-base", function (g, d, el){ 134 | var backup, base, 135 | q = d.createElement("q"), 136 | head = d.getElementsByTagName("head")[0], 137 | href = location.href, 138 | fake = false, 139 | supported = null, 140 | token = location.search || location.hash; 141 | 142 | if(head){ 143 | base = d.getElementsByTagName("base")[0] || (function(){ 144 | fake = true; 145 | return head.insertBefore(d.createElement("base"), head.firstChild); 146 | })(); 147 | 148 | backup = base.href || href.slice(0, token ? href.indexOf(token) : href.length).replace(/[^\/]*$/, ""); 149 | base.href = location.protocol + "//x"; 150 | q.cite = "y"; 151 | supported = q.cite.indexOf("x/y") > -1; 152 | 153 | // reset href before removal, otherwise href persists in Opera 154 | base.href = backup; 155 | if(fake){ 156 | head.removeChild(base); 157 | } 158 | } 159 | return supported; 160 | }); 161 | 162 | addtest("dom-classlist", function(g, d, e){ 163 | var iht = has.isHostType; 164 | if(!iht(e, "classList")){ 165 | return false; 166 | } 167 | 168 | var cl = e.classList; 169 | 170 | return iht(cl, "item") && iht(cl, "contains") && iht(cl, "add") && 171 | iht(cl, "remove") && iht(cl, "toggle"); 172 | }); 173 | 174 | return has; 175 | }); 176 | })(typeof define != "undefined" ? define : function(deps, factory){ 177 | factory(has); // the use global has() if a module system is not available 178 | }); 179 | 180 | -------------------------------------------------------------------------------- /has.js: -------------------------------------------------------------------------------- 1 | (function(define, g){ 2 | define(["./_plugin"], function(plugin){ 3 | // summary: A simple feature detection function/framework. 4 | // 5 | // name: String 6 | // The name of the feature to detect, as defined by the overall `has` tests. 7 | // Tests can be registered via `has.add(testname, testfunction)`. 8 | // 9 | // example: 10 | // mylibrary.bind = has("native-bind") ? function(fn, context){ 11 | // return fn.bind(context); 12 | // } : function(fn, context){ 13 | // return function(){ 14 | // fn.apply(context, arguments); 15 | // } 16 | // } 17 | 18 | var NON_HOST_TYPES = { "boolean": 1, "number": 1, "string": 1, "undefined": 1 }, 19 | VENDOR_PREFIXES = ["Webkit", "Moz", "O", "ms", "Khtml"], 20 | d = isHostType(g, "document") && g.document, 21 | el = d && isHostType(d, "createElement") && d.createElement("DiV"), 22 | testCache = {} 23 | ; 24 | 25 | function has(/* String */name){ 26 | if(typeof testCache[name] == "function"){ 27 | testCache[name] = testCache[name](g, d, el); 28 | } 29 | return testCache[name]; // Boolean 30 | } 31 | 32 | function add(/* String */name, /* Function */test, /* Boolean? */now){ 33 | // summary: Register a new feature detection test for some named feature 34 | // 35 | // name: String 36 | // The name of the feature to test. 37 | // 38 | // test: Function 39 | // A test function to register. If a function, queued for testing until actually 40 | // needed. The test function should return a boolean indicating 41 | // the presence of a feature or bug. 42 | // 43 | // now: Boolean? 44 | // Optional. Omit if `test` is not a function. Provides a way to immediately 45 | // run the test and cache the result. 46 | // example: 47 | // A redundant test, testFn with immediate execution: 48 | // | has.add("javascript", function(){ return true; }, true); 49 | // 50 | // example: 51 | // Again with the redundantness. You can do this in your tests, but we should 52 | // not be doing this in any internal has.js tests 53 | // | has.add("javascript", true); 54 | // 55 | // example: 56 | // Three things are passed to the testFunction. `global`, `document`, and a generic element 57 | // from which to work your test should the need arise. 58 | // | has.add("bug-byid", function(g, d, el){ 59 | // | // g == global, typically window, yadda yadda 60 | // | // d == document object 61 | // | // el == the generic element. a `has` element. 62 | // | return false; // fake test, byid-when-form-has-name-matching-an-id is slightly longer 63 | // | }); 64 | testCache[name] = now ? test(g, d, el) : test; 65 | } 66 | 67 | // cssprop adapted from http://gist.github.com/598008 (thanks, ^pi) 68 | function cssprop(name, el){ 69 | var supported = false, 70 | capitalized = name.charAt(0).toUpperCase() + name.slice(1), 71 | length = VENDOR_PREFIXES.length, 72 | style = el.style; 73 | 74 | if(typeof style[name] == "string"){ 75 | supported = true; 76 | }else{ 77 | while(length--){ 78 | if(typeof style[VENDOR_PREFIXES[length] + capitalized] == "string"){ 79 | supported = true; 80 | break; 81 | } 82 | } 83 | } 84 | return supported; 85 | } 86 | 87 | function clearElement(el){ 88 | if(el){ 89 | while(el.lastChild){ 90 | el.removeChild(el.lastChild); 91 | } 92 | } 93 | return el; 94 | } 95 | 96 | // Host objects can return type values that are different from their actual 97 | // data type. The objects we are concerned with usually return non-primitive 98 | // types of object, function, or unknown. 99 | function isHostType(object, property){ 100 | var type = typeof object[property]; 101 | return type == 'object' ? !!object[property] : !NON_HOST_TYPES[type]; 102 | } 103 | 104 | //>>excludeStart("production", true); 105 | function all(){ 106 | // summary: For debugging or logging, can be removed in production. Run all known tests 107 | // at some point in time for the current environment. 108 | var name, ret = {}; 109 | for(name in testCache){ 110 | try{ 111 | ret[name] = has(name); 112 | }catch(e){ 113 | ret[name] = "error"; 114 | ret[name].ERROR_MSG = e.toString(); 115 | } 116 | } 117 | return ret; 118 | } 119 | 120 | has.all = all; 121 | has.pluginBuilder = "./build/plugin"; 122 | //>>excludeEnd("production"); 123 | has.add = add; 124 | has.load = plugin; 125 | has.clearElement = clearElement; 126 | has.cssprop = cssprop; 127 | has.isHostType = isHostType; 128 | has._tests = testCache; 129 | 130 | has.add("dom", function(g, d, el){ 131 | return d && el && isHostType(g, "location") && isHostType(d, "documentElement") && 132 | isHostType(d, "getElementById") && isHostType(d, "getElementsByName") && 133 | isHostType(d, "getElementsByTagName") && isHostType(d, "createComment") && 134 | isHostType(d, "createElement") && isHostType(d, "createTextNode") && 135 | isHostType(el, "appendChild") && isHostType(el, "insertBefore") && 136 | isHostType(el, "removeChild") && isHostType(el, "getAttribute") && 137 | isHostType(el, "setAttribute") && isHostType(el, "removeAttribute") && 138 | isHostType(el, "style") && typeof el.style.cssText == "string"; 139 | }, true); 140 | 141 | // Stop repeat background-image requests and reduce memory consumption in IE6 SP1 142 | // http://misterpixel.blogspot.com/2006/09/forensic-analysis-of-ie6.html 143 | // http://blogs.msdn.com/b/cwilso/archive/2006/11/07/ie-re-downloading-background-images.aspx?PageIndex=1 144 | // http://support.microsoft.com/kb/823727 145 | try{ 146 | document.execCommand("BackgroundImageCache", false, true); 147 | }catch(e){} 148 | 149 | return has; 150 | 151 | }); 152 | })(typeof define != "undefined" ? 153 | define : // if define() is available, load has as a module (no global) 154 | function(deps, factory){ 155 | // if directly loaded, make has a global 156 | has = factory(); 157 | }, this); 158 | -------------------------------------------------------------------------------- /tests/runTests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 30 | 31 | has.js tests 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 |

has.js

58 | 59 |

Feature Detection, a la carte stylie

60 | 61 |
62 | 63 | 170 | 171 |
172 | Results for category base-uri 173 | 178 |
179 | 180 |

Test running took

181 |

Your userAgent is being reported as:

182 | 183 |
184 | 185 |
186 | 187 | 188 |
189 | 190 |

More about this test page and project on github:has.js

191 | 192 | 193 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # has.js 2 | 3 | Pure feature detection library, a la carte style. 4 | 5 | This document is **not** complete. 6 | 7 | ## About 8 | 9 | Browser sniffing and feature inference are flawed techniques for detecting browser support in client side JavaScript. The goal of 10 | **has.js** is to provide a collection of self-contained tests and unified framework around using pure feature detection for whatever 11 | library consumes it. 12 | 13 | You likely _won't_ see **has.js** as a public API in any library. The idea is that _%yourfavoritelibrary%_ will import some or all 14 | the available tests, based on their own needs for providing you a normalized future proof API against common tasks. 15 | 16 | **not stable**, so keep that in mind. This is a young project, and the decided naming conventions may be a moving target. 17 | The tests are nothing that haven't been done over and over in various places, 18 | so the intention is to come to some agreement on a basic naming convention and API based on real world use cases. 19 | 20 | Currently, the testing convention is _has('somefeature')_ returns _Boolean_. eg: 21 | 22 | if(has("function-bind")){ 23 | // your enviroment has a native Function.prototype.bind 24 | }else{ 25 | // you should get a new browser. 26 | } 27 | 28 | In the real world, this may translate into something like: 29 | 30 | mylibrary.trim = has("string-trim") ? function(str){ 31 | return (str || "").trim(); 32 | } : function(str){ 33 | /* do the regexp based string trimming you feel like using */ 34 | } 35 | 36 | By doing this, we can easily defer to browser-native versions of common functions, augment prototypes (which **has.js** will _not_ do) to 37 | supplement the natives, or whatever we choose. 38 | 39 | Running _has()_ is a one-time cost, deferred until needed. After first run, subsequent _has()_ checks are cached and return immediately. 40 | 41 | ## Testing Registration 42 | 43 | Each test is self-contained. Register a test with _has.add()_: 44 | 45 | has.add("some-test-name", function(global, document, anElement){ 46 | // global is a reference to global scope, document is the same 47 | // anElement only exists in browser enviroments, and can be used 48 | // as a common element from which to do DOM working. 49 | // ALWAYS CLEAN UP AFTER YOURSELF in a test. No leaks, thanks. 50 | // return a Boolean from here. 51 | return true; 52 | }); 53 | 54 | You can register and run a test immediately by passing a truthy value after the test function: 55 | 56 | has.add("some-other-test", function(){ 57 | return false; // Boolean 58 | }, true) 59 | 60 | This is preferred over what would seem a much more effective version: 61 | 62 | // this is not wrapped in a function, and should be: 63 | has.add("some-other-test", ("foo" in bar)); // or whatever 64 | 65 | By forcing a function wrapper around the test logic we are able to defer execution until needed, as well as provide a normalized way for 66 | each test to have it's own execution context. This way, we can remove some or all the tests we do not need in whatever upstream library 67 | should adopt _has_. 68 | 69 | ## As a Module 70 | has.js can be loaded as a module with RequireJS, Dojo, or any other AMD module 71 | loader, and no global variable will be created. Requiring or depending on has.js give 72 | you access to has(). You can also depend on any of the detect modules and has will 73 | be included. For example, we could create a module that depends on has/array: 74 | 75 | define(["has/array"], function(has){ 76 | if(has("array-every")){ 77 | someArray.every(...); 78 | } 79 | ... 80 | }); 81 | 82 | Make sure that you have has.js setup as a package in order for this work properly though. 83 | You can setup has.js as a package in RequireJS with something like: 84 | 85 | packages: [ 86 | { 87 | name:"has", 88 | location:"has.js", 89 | lib:"./detect", 90 | main:"../has" 91 | } 92 | ] 93 | 94 | ## As a Dependency Plugin 95 | 96 | has.js can also be used as a dependency loader plugin for AMD module loaders. This allows 97 | you to conditionally load modules based on available features. The syntax for feature-dependent 98 | modules is based on the JavaScript ternary operator: 99 | 100 | define(["has!feature?module-if-has-feature:module-if-does-not-have-feature",... 101 | 102 | This follows the standard rule for ternary operators. If the feature (the token before the 103 | ?) is available, the module before the : is loaded, otherwise the module after the colon 104 | is loaded. Ternary operators can also be nested. We can have a set of different modules 105 | based on different features: 106 | 107 | define(["has!feature1?module1:feature2?module2:feature3?module3:default-module",... 108 | 109 | If feature1 is available, module1 will be returned, if feature2 is available, module2 will be returned, etc. 110 | 111 | Also, if no module (empty string) is provided in one of the ternary slots, undefined will be returned. For example: 112 | 113 | define(["has!feature?module-if-has-feature",... 114 | 115 | This will return the module "module-if-has-feature" if the feature is available, otherwise 116 | it will return undefined. 117 | 118 | The "has" module itself does not register any tests. Normally you would reference the 119 | has detection module that registers the needed tests to ensure that the correct tests 120 | are available. For example, if you want to branch to different modules based on the 121 | existence of the the Array's every() method, you could do: 122 | 123 | define(["has/array!array-every?module-for-every:module-when-no-every",... 124 | 125 | Or more likely, you can also create your test registration module, then use this module as the 126 | branching module. For example, we could create a test registration module: 127 | 128 | define("my-tests", ["has"], function(has){ 129 | has.add("some-test", function(){ 130 | // the test code 131 | }); 132 | }); 133 | 134 | And then we could use it: 135 | 136 | define(["my-tests!some-test?module-a:module-b"], function(... 137 | 138 | 139 | ## Platform Builds 140 | 141 | Something resembling a "builder" is coming. A basic dependency matcher and test lister is provided in `build/` 142 | 143 | ## Contribute 144 | 145 | **has.js** contributions are covered under a common license by way of [Dojo Foundation CLA](http://dojofoundation.org/cla), and brought to you by the following awesome folks: 146 | 147 | + [John David Dalton](http://allyoucanleet.com/) - FuseJS Project Lead 148 | + [Brad Dougherty](http://github.com/bdougherty) 149 | + [Bryan Forbes](http://http://www.reigndropsfall.net) - Dojo Committer 150 | + [Ryan Grove](http://twitter.com/yaypie) - YUI engineer 151 | + [Andrée Hansson](http://github.com/peol) 152 | + [Peter Higgins](http://higginsforpresident.net) - Dojo Project Lead 153 | + [Paul Irish](http://paulirish.com) - jQuery Team, Modernizr developer 154 | + [Weston Ruter](http://weston.ruter.net/) - X-Team/XHTMLized developer 155 | + [Rick Waldron](http://github.com/rwldrn/) 156 | + [Juriy Zaytsev](http://perfectionkills.com) - @kangax, nuff' said. 157 | 158 | There is an _irc_ room setup for discussion or questions: **#hasjs@irc.freenode.net** 159 | 160 | ## Conventions 161 | 162 | Internally, we follow these conventions: 163 | 164 | + All Strings are quoted using double-quotes **"** 165 | + Test names are lowercase, hyphen separated strings. Enclosed in double-quotes 166 | + Tests are passed `g`, `d`, and `n`. Use these aliases always. 167 | + Globals are as follows, available as used but will be reduced to a single ref: 168 | + STR == "string" 169 | + FN == "function" 170 | + Tests return Booleans. Sometimes, you must coerce a boolean: 171 | + DO return !!(someExpression) as necessary 172 | + DO N0T return !!("x" in y) or anything else that would otherwise return a boolean, eg 173 | + x !== y, x > y, x typeof y 174 | + DO wrap expressions in parens: eg return ("x" in y) 175 | 176 | ## License 177 | 178 | Tentatively, **has.js** is available under the Academic Free License, New BSD License, and the MIT License. Providing this common code under multiple licenses requires us to have all contributors agree to a [CLA](http://dojofoundation.org/cla). 179 | 180 | ## TODO 181 | 182 | **has.js** is open source, and open to contribution. Please fork and send pull requests as you see fit. This is a rough list of things that are needed or coming: 183 | 184 | + moar tests. Fork/pull request anytime. 185 | + Static Frontend - some home to put a static instance of has.js online to collect UA -> has(test) mappings 186 | + Documentation regarding each of the tests, by name. eg: has("native-xhr") // tests if the environment has a native XmlHttpRequest implementation. 187 | + moar tests. Again with the forking. 188 | + "compiler" code / frontend 189 | + ideally something that will use the list of tests, provide a clean interface to selecting tests needed and to download a single has.js file with tests embedded. 190 | + keeping in mind to remove additional closures and provide (only needed) var CONTS = "" style helpers in a single wrapping function. -------------------------------------------------------------------------------- /detect/bugs.js: -------------------------------------------------------------------------------- 1 | (function(define){ 2 | define(["has", "./strings", "./dom"], function(has){ 3 | 4 | // http://github.com/kangax/cft 5 | var toString = {}.toString, 6 | addtest = has.add, 7 | STR = "string", 8 | FN = "function", 9 | FUNCTION_CLASS = "[object Function]" 10 | ; 11 | 12 | function testForIn(value){ 13 | var i, 14 | count = 0, 15 | klass = function(){ this.toString = 1; }; 16 | 17 | for(i in new klass){ 18 | count++; 19 | } 20 | return count == value; 21 | } 22 | 23 | // true for IE < 9 24 | addtest("bug-for-in-skips-shadowed", function(){ 25 | return testForIn(0); 26 | }); 27 | 28 | // true for Safari 2 29 | addtest("bug-for-in-repeats-shadowed", function(){ 30 | return testForIn(2); 31 | }); 32 | 33 | addtest("bug-string-split-regexp", function(){ 34 | var buggy = null, s = "a_b"; 35 | if(toString.call(s.split) == FUNCTION_CLASS){ 36 | buggy = s.split(/(_)/).length != 3; 37 | } 38 | return buggy; 39 | }); 40 | 41 | addtest("bug-function-expression", function(){ 42 | // `x` should be resolved to `null` (the one we declared outside this function) 43 | // but since named function expression identifier leaks onto the enclosing scope in IE, 44 | // it will be resolved to a function 45 | var f = function x(){}, 46 | buggy = typeof x == FN; 47 | if(buggy){ 48 | x = null; 49 | } 50 | return buggy; 51 | }); 52 | 53 | addtest("bug-string-replace-ignores-functions", function(){ 54 | var buggy = null, s = "a"; 55 | if(toString.call(s.replace) == FUNCTION_CLASS){ 56 | buggy = s.replace(s, function(){ return ""; }) != ""; 57 | } 58 | return buggy; 59 | }); 60 | 61 | addtest("bug-arguments-instanceof-array", function(g){ 62 | return arguments instanceof g.Array; 63 | }); 64 | 65 | addtest("bug-array-concat-arguments", function(){ 66 | return (function(){ 67 | var buggy = null; 68 | if(has("bug-arguments-instanceof-array")){ 69 | buggy = [].concat(arguments)[0] != 1; 70 | } 71 | return buggy; 72 | })(1,2); 73 | }); 74 | 75 | // ES5 added (\uFEFF) as a whitespace character 76 | var whitespace = "\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u180E\u2000\u2001\u2002"+ 77 | "\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028\u2029\uFEFF"; 78 | 79 | addtest("bug-es5-trim", function(){ 80 | var buggy = null; 81 | if(has("string-trim")){ 82 | buggy = !!whitespace.trim(); 83 | } 84 | return buggy; 85 | }); 86 | 87 | addtest("bug-es5-regexp", function(){ 88 | return !(/^\s+$/).test(whitespace); 89 | }); 90 | 91 | addtest("bug-tofixed-rounding", function(){ 92 | return (.9).toFixed() == 0; 93 | }); 94 | 95 | if(!has("dom")){ return; } 96 | 97 | addtest("bug-offset-values-positioned-inside-static", function(g, d, el){ 98 | var div, fake, 99 | buggy = null, 100 | de = d.documentElement, 101 | id = "__test_" + Number(new Date()), 102 | css = "margin:0;padding:0;border:0;visibility:hidden;", 103 | root = d.body || (function(){ 104 | fake = true; 105 | return de.insertBefore(d.createElement("body"), de.firstChild); 106 | }()); 107 | 108 | el.innerHTML = 109 | "
"+ 110 | "
"+ 111 | "
<\/div>"+ 112 | "
x<\/div>"+ 113 | "<\/div>"+ 114 | "<\/div>"; 115 | 116 | root.insertBefore(el, root.firstChild); 117 | div = d.getElementById(id); 118 | 119 | if(div.firstChild){ 120 | buggy = false; 121 | if(div.offsetTop != 10){ 122 | // buggy, set position to relative and check if it fixes it 123 | div.style.position = "relative"; 124 | if(div.offsetTop == 10){ 125 | buggy = true; 126 | } 127 | } 128 | } 129 | if(fake){ 130 | root.parentNode.removeChild(root); 131 | } 132 | root.removeChild(el); 133 | has.clearElement(el); 134 | return buggy; 135 | }); 136 | 137 | // Opera 9.x (possibly other versions as well) returns actual values (instead of "auto") 138 | // for statically positioned elements 139 | addtest("bug-computed-values-for-static", function(g, d){ 140 | var cs, view, 141 | de = d.documentElement, 142 | style = d.style, 143 | position = null, 144 | buggy = position; 145 | 146 | if(has("dom-computed-style")){ 147 | // if element is not statically positioned, make it as such, then restore 148 | view = d.defaultView; 149 | cs = view.getComputedStyle(de, null); 150 | if(cs.position != "static"){ 151 | position = cs.position; 152 | style.position = ""; 153 | } 154 | buggy = !!(buggy = view.getComputedStyle(de, null)) && buggy.left != "auto"; 155 | if(position !== null){ 156 | style.position = position; 157 | } 158 | } 159 | return buggy; 160 | }); 161 | 162 | addtest("bug-computed-style-hidden-zero-height", function(g, d){ 163 | var cs, 164 | de = d.documentElement, 165 | style = de.style, 166 | display = style.display, 167 | buggy = null; 168 | 169 | if(has("dom-computed-style")){ 170 | style.display = "none"; 171 | cs = d.defaultView.getComputedStyle(de, null); 172 | buggy = cs && cs.height == "0px"; 173 | style.display = display; 174 | } 175 | return buggy; 176 | }); 177 | 178 | addtest("bug-root-children-not-styled", function(g, d, el){ 179 | var buggy = null, 180 | de = d.documentElement; 181 | 182 | el.style.cssText = "width:40px;height:40px;"; 183 | 184 | try{ 185 | de.insertBefore(el, de.firstChild); 186 | buggy = el.clientWidth == 0; 187 | de.removeChild(el); 188 | }catch(e){} 189 | 190 | el.style.cssText = ""; 191 | return buggy; 192 | }); 193 | 194 | addtest("bug-contains", function(g, d, el){ 195 | var buggy = null, 196 | e2 = d.createElement("div"); 197 | 198 | if(has.isHostType(el, "contains")){ 199 | buggy = el.contains(e2); 200 | } 201 | return buggy; 202 | }); 203 | 204 | addtest("bug-query-selector-ignores-caps", function(g, d, el){ 205 | var e2, buggy = null; 206 | 207 | if(d.compatMode == "BackCompat" && has.isHostType(el, "querySelector")){ 208 | e2 = d.createElement("span"); 209 | e2.className = "Test"; 210 | el.appendChild(e2); 211 | buggy = (el.querySelector(".Test") != null); 212 | el.removeChild(e2); 213 | } 214 | return buggy; 215 | }); 216 | 217 | // true for Safari 218 | addtest("bug-typeof-nodelist-function", function(g, d){ 219 | return (typeof d.documentElement.childNodes == FN); 220 | }); 221 | 222 | // true for IE 223 | addtest("bug-getelementsbytagname-returns-comment-nodes", function(g, d, el){ 224 | var all, buggy = null; 225 | 226 | el.appendChild(d.createElement("span")).appendChild(d.createTextNode("a")); 227 | el.appendChild(d.createComment("b")); 228 | all = el.getElementsByTagName("*"); 229 | 230 | // IE5.5 returns a 0-length collection when calling getElementsByTagName with wildcard 231 | if(all.length){ 232 | buggy = !!all[1] && all[1].nodeType != 1; 233 | } 234 | has.clearElement(el); 235 | return buggy; 236 | }); 237 | 238 | // name attribute can not be set at run time in IE < 8 239 | // http://msdn.microsoft.com/en-us/library/ms536389.aspx 240 | addtest("bug-readonly-element-name", function(g, d, el){ 241 | var buggy, 242 | input = el.appendChild(d.createElement("input")); 243 | 244 | input.name = 'x'; 245 | buggy = !el.getElementsByTagName('*')['x']; 246 | has.clearElement(el); 247 | return buggy; 248 | }); 249 | 250 | // type attribute can only be set once and cannot be changed once in DOM 251 | // http://msdn.microsoft.com/en-us/library/ms534700.aspx 252 | addtest("bug-readonly-element-type", function(g, d, el){ 253 | var buggy = true, 254 | input = el.appendChild(d.createElement("input")); 255 | 256 | input.type = 'text'; 257 | try { 258 | input.type = 'password'; 259 | buggy = input.type != 'password'; 260 | } catch (e) { } 261 | has.clearElement(el); 262 | return buggy; 263 | }); 264 | 265 | // true for IE 266 | addtest("bug-properties-are-attributes", function(g, d, el){ 267 | el.__foo = "bar"; 268 | var buggy = el.getAttribute("__foo") == "bar"; 269 | 270 | if(buggy){ 271 | el.removeAttribute("__foo"); 272 | }else{ 273 | delete el.__foo; 274 | } 275 | return buggy; 276 | }); 277 | 278 | // true for IE 279 | addtest("bug-pre-ignores-newline", function(g, d){ 280 | var buggy, 281 | de = d.documentElement, 282 | el = de.appendChild(d.createElement("pre")), 283 | txt = el.appendChild(d.createTextNode("xx")), 284 | initialHeight = el.offsetHeight; 285 | 286 | el.firstChild.nodeValue = "x\nx"; 287 | // check if `offsetHeight` changed after adding "\n" to the value 288 | buggy = el.offsetHeight == initialHeight; 289 | de.removeChild(el); 290 | return buggy; 291 | }); 292 | 293 | addtest("bug-select-innerhtml", function(g, d, el){ 294 | var buggy = true; 295 | el = d.createElement("select"); 296 | el.innerHTML = "