├── README ├── feature_tests.js ├── getStyleProperty.js ├── index.html ├── master.css ├── master.js └── style.html /README: -------------------------------------------------------------------------------- 1 | Common Feature Tests (CFT) aims to provide a convenient set of feature tests (duh!) for Javascript (and JScript). It eliminates a need for browser sniffing, providing a much more robust way to work around environment quirks. None of the tests require presence of `document.body` and so can be run at any time. 2 | 3 | A "live" version is at http://kangax.github.io/cft/ 4 | -------------------------------------------------------------------------------- /feature_tests.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (c) 2008 Juriy Zaytsev (kangax@gmail.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 13 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 14 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 15 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 16 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 18 | SOFTWARE. 19 | 20 | */ 21 | 22 | (function(__global){ 23 | 24 | var t = new Date(); 25 | 26 | // make sure `window` resolves to a global object 27 | var window = this; 28 | 29 | var features = { }; 30 | var bugs = { }; 31 | 32 | features.IS_CSS_TRANSFORMATION_SUPPORTED = (features.__IS_CSS_TRANSFORMATION_SUPPORTED = function(){ 33 | var docEl = document.documentElement, s; 34 | if (docEl && (s = docEl.style)) { 35 | return ( 36 | typeof s.WebkitTransform == 'string' || 37 | typeof s.MozTransform == 'string' || 38 | typeof s.OTransform == 'string' || 39 | typeof s.MsTransform == 'string' || 40 | typeof s.transform == 'string' 41 | ); 42 | } 43 | return null; 44 | })(); 45 | 46 | features.IS_ELEMENT_TAGNAME_UPPERCASED = (features.__IS_ELEMENT_TAGNAME_UPPERCASED = function(){ 47 | var docEl = document.documentElement; 48 | if (docEl) { 49 | return 'HTML' === docEl.nodeName; 50 | } 51 | return null; 52 | })(); 53 | 54 | bugs.QUERY_SELECTOR_IGNORES_CAPITALIZED_VALUES = (bugs.__QUERY_SELECTOR_IGNORES_CAPITALIZED_VALUES = function(){ 55 | if (document.createElement && (document.compatMode === 'BackCompat')) { 56 | var el = document.createElement('div'), 57 | el2 = document.createElement('span'); 58 | if (el && el2 && el.appendChild && el.querySelector) { 59 | el2.className = 'Test'; 60 | el.appendChild(el2); 61 | var isBuggy = (el.querySelector('.Test') !== null); 62 | el = el2 = null; 63 | return isBuggy; 64 | } 65 | } 66 | return null; 67 | })(); 68 | 69 | features.ARRAY_PROTOTYPE_SLICE_CAN_CONVERT_NODELIST_TO_ARRAY = (features.__ARRAY_PROTOTYPE_SLICE_CAN_CONVERT_NODELIST_TO_ARRAY = function(){ 70 | try { 71 | return (Array.prototype.slice.call(document.forms, 0) instanceof Array); 72 | } 73 | catch(e) { 74 | return false; 75 | } 76 | })(); 77 | 78 | features.WINDOW_EVAL_EVALUATES_IN_GLOBAL_SCOPE = (features.__WINDOW_EVAL_EVALUATES_IN_GLOBAL_SCOPE = function(){ 79 | var fnId = '__eval' + Number(new Date()), 80 | passed = false; 81 | 82 | try { 83 | // catch indirect eval call errors (i.e. in such clients as Blackberry 9530) 84 | window.eval('var ' + fnId + '=true'); 85 | } catch(e) { } 86 | passed = (window[fnId] === true); 87 | if (passed) { 88 | try { 89 | delete window[fnId]; 90 | } 91 | catch(e) { 92 | window[fnId] = void 0; 93 | } 94 | } 95 | return passed; 96 | })(); 97 | 98 | ( features.__IS_EVENT_METAKEY_PRESENT = 99 | features.__IS_EVENT_PREVENTDEFAULT_PRESENT = 100 | features.__IS_EVENT_SRCELEMENT_PRESENT = 101 | features.__IS_EVENT_RELATEDTARGET_PRESENT = function(){ 102 | 103 | features.IS_EVENT_METAKEY_PRESENT = null; 104 | features.IS_EVENT_PREVENTDEFAULT_PRESENT = null; 105 | features.IS_EVENT_SRCELEMENT_PRESENT = null; 106 | features.IS_EVENT_RELATEDTARGET_PRESENT = null; 107 | 108 | if (document.createElement) { 109 | var i = document.createElement('input'), 110 | root = document.documentElement; 111 | if (i && i.style && i.click && root && root.appendChild && root.removeChild) { 112 | i.type = 'checkbox'; 113 | i.style.display = 'none'; 114 | i.onclick = function(e) { 115 | e = e || window.event; 116 | features.IS_EVENT_METAKEY_PRESENT = ('metaKey' in e); 117 | features.IS_EVENT_PREVENTDEFAULT_PRESENT = ('preventDefault' in e); 118 | features.IS_EVENT_SRCELEMENT_PRESENT = ('srcElement' in e); 119 | features.IS_EVENT_RELATEDTARGET_PRESENT = ('relatedTarget' in e); 120 | }; 121 | root.appendChild(i); 122 | i.click(); 123 | root.removeChild(i); 124 | i.onclick = null; 125 | i = null; 126 | } 127 | } 128 | })(); 129 | 130 | features.IS_NATIVE_HAS_ATTRIBUTE_PRESENT = (features.__IS_NATIVE_HAS_ATTRIBUTE_PRESENT = function(){ 131 | if (document.createElement) { 132 | var i = document.createElement('iframe'), 133 | root = document.documentElement, 134 | frames = window.frames; 135 | if (root && root.appendChild && root.removeChild) { 136 | i.style.display = 'none'; 137 | root.appendChild(i); 138 | // some clients (e.g. Blackberry 9000 (Bold)) throw error when accesing frame's document 139 | try { 140 | var frame = frames[frames.length-1]; 141 | if (frame) { 142 | var doc = frame.document; 143 | if (doc && doc.write) { 144 | doc.write(''); 145 | var isPresent = doc.documentElement ? ('hasAttribute' in doc.documentElement) : null; 146 | root.removeChild(i); 147 | i = null; 148 | return isPresent; 149 | } 150 | } 151 | } catch(e) { 152 | return null; 153 | } 154 | } 155 | } 156 | return null; 157 | })(); 158 | 159 | features.IS_POSITION_FIXED_SUPPORTED = (features.__IS_POSITION_FIXED_SUPPORTED = function(){ 160 | var container = document.body; 161 | 162 | if (document.createElement && container && container.appendChild && container.removeChild) { 163 | var el = document.createElement('div'); 164 | 165 | if (!el.getBoundingClientRect) return null; 166 | 167 | el.innerHTML = 'x'; 168 | el.style.cssText = 'position:fixed;top:100px;'; 169 | container.appendChild(el); 170 | 171 | var originalHeight = container.style.height, 172 | originalScrollTop = container.scrollTop; 173 | // In IE<=7, the window's upper-left is at 2,2 (pixels) with respect to the true client. 174 | // surprisely, in IE8, the window's upper-left is at -2, -2 (pixels), but other elements 175 | // tested is just right, so we need adjust this. 176 | // https://groups.google.com/forum/?fromgroups#!topic/comp.lang.javascript/zWJaFM5gMIQ 177 | // https://bugzilla.mozilla.org/show_bug.cgi?id=174397 178 | var extraTop = document.documentElement.getBoundingClientRect().top; 179 | extraTop = extraTop > 0 ? extraTop : 0; 180 | 181 | container.style.height = '3000px'; 182 | container.scrollTop = 500; 183 | 184 | var elementTop = el.getBoundingClientRect().top; 185 | container.style.height = originalHeight; 186 | 187 | var isSupported = (elementTop - extraTop) === 100; 188 | container.removeChild(el); 189 | container.scrollTop = originalScrollTop; 190 | 191 | return isSupported; 192 | } 193 | return null; 194 | })(); 195 | 196 | // Thanks to David Mark for suggestion 197 | features.IS_CONTEXTMENU_EVENT_SUPPORTED = (features.__IS_CONTEXTMENU_EVENT_SUPPORTED = function(){ 198 | var isPresent = null; 199 | if (document.createElement) { 200 | var el = document.createElement('p'); 201 | if (el && el.setAttribute) { 202 | el.setAttribute('oncontextmenu', ''); 203 | isPresent = (typeof el.oncontextmenu != 'undefined'); 204 | } 205 | } 206 | return isPresent; 207 | })(); 208 | 209 | // Opera 9.x (possibly other versions as well) returns actual values (instead of "auto") 210 | // for statically positioned elements 211 | features.COMPUTED_STYLE_RETURNS_VALUES_FOR_STATICLY_POSITIONED_ELEMENTS = ( 212 | features.__COMPUTED_STYLE_RETURNS_VALUES_FOR_STATICLY_POSITIONED_ELEMENTS = function(){ 213 | var view = document.defaultView; 214 | if (view && view.getComputedStyle) { 215 | var docEl = document.documentElement; 216 | var position = null; 217 | var style = view.getComputedStyle(docEl, null); 218 | // if element is not statically positioned, make it as such, then restore 219 | if (style.position !== 'static') { 220 | position = style.position; 221 | docEl.style.position = ''; 222 | } 223 | var result = (view.getComputedStyle(docEl, null).left !== 'auto'); 224 | if (position !== null) { 225 | docEl.style.position = position; 226 | } 227 | return result; 228 | } 229 | return null; 230 | })(); 231 | 232 | features.IS_RGBA_SUPPORTED = (features.__IS_RGBA_SUPPORTED = function(){ 233 | var result = null; 234 | if (document.createElement) { 235 | var value = 'rgba(1,1,1,0.5)', 236 | el = document.createElement('p'), 237 | re = /^rgba/; 238 | if (el && el.style && typeof re.test == 'function') { 239 | try { 240 | el.style.color = value; 241 | result = re.test(el.style.color); 242 | } 243 | catch(e) { 244 | result = false; 245 | } 246 | } 247 | } 248 | return result; 249 | })(); 250 | 251 | features.IS_CSS_BORDER_RADIUS_SUPPORTED = (features.__IS_CSS_BORDER_RADIUS_SUPPORTED = function(){ 252 | var docEl = document.documentElement, s; 253 | if (docEl && (s = docEl.style)) { 254 | return (typeof s.borderRadius == 'string' 255 | || typeof s.MozBorderRadius == 'string' 256 | || typeof s.WebkitBorderRadius == 'string' 257 | || typeof s.KhtmlBorderRadius == 'string'); 258 | } 259 | return null; 260 | })(); 261 | 262 | features.IS_CANVAS_SUPPORTED = (features.__IS_CANVAS_SUPPORTED = function(){ 263 | if (document.createElement) { 264 | var elCanvas = document.createElement('canvas'); 265 | return !!(elCanvas && elCanvas.getContext && elCanvas.getContext('2d')); 266 | } 267 | return null; 268 | })(); 269 | 270 | features.ELEMENT_CHILDREN_RETURNS_ELEMENT_NODES = (features.__ELEMENT_CHILDREN_RETURNS_ELEMENT_NODES = function(){ 271 | var isSupported = null, 272 | docEl = document.documentElement; 273 | if (document.createElement && typeof docEl.children != 'undefined') { 274 | var el = document.createElement('div'); 275 | el.innerHTML = '

a<\/p><\/div>b'; 276 | // Safari 2.x returns ALL elements in `children` 277 | // We check that first element is a DIV and that it's the only one element returned 278 | isSupported = (el.children && 279 | el.children.length === 1 && 280 | el.children[0] && 281 | el.children[0].tagName && 282 | el.children[0].tagName.toUpperCase() === 'DIV'); 283 | } 284 | return isSupported; 285 | })(); 286 | 287 | features.IS_CSS_ENABLED = (features.__IS_CSS_ENABLED = function(){ 288 | var body = document.body, 289 | isSupported = null; 290 | if (document.createElement && 291 | body && 292 | body.appendChild && 293 | body.removeChild) { 294 | var el = document.createElement('div'); 295 | if (el && el.style) { 296 | el.style.display = 'none'; 297 | body.appendChild(el); 298 | isSupported = (el.offsetWidth === 0); 299 | body.removeChild(el); 300 | } 301 | } 302 | return isSupported; 303 | })(); 304 | 305 | // features.IS_DOMFOCUSIN_SUPPORTED = (features.__IS_DOMFOCUSIN_SUPPORTED = function(){ 306 | // var body = document.body, 307 | // isSupported = null; 308 | // if (document.createElement && 309 | // body && 310 | // body.insertBefore && 311 | // body.removeChild) { 312 | // var el = document.createElement('DIV'); 313 | // if (el && el.addEventListener && el.focus) { 314 | // isSupported = false; 315 | // el.addEventListener('DOMFocusIn', function(){ isSupported = true; }, false); 316 | // el.tabIndex = -1; 317 | // body.insertBefore(el, body.firstChild); 318 | // el.focus(); 319 | // body.removeChild(el); 320 | // } 321 | // } 322 | // return isSupported; 323 | // })(); 324 | 325 | features.IS_QUIRKS_MODE = (features.__IS_QUIRKS_MODE = function(){ 326 | if (document.createElement) { 327 | var el = document.createElement('div'); 328 | if (el && el.style) { 329 | el.style.width = '1'; 330 | } 331 | return el.style.width === '1px'; 332 | } 333 | })(); 334 | 335 | features.IS_CONTAINS_BUGGY = (features.__IS_CONTAINS_BUGGY = function(){ 336 | if (document.createElement) { 337 | var el1 = document.createElement('div'), 338 | el2 = document.createElement('div'); 339 | if (el1 && el2 && el1.contains) { 340 | return el1.contains(el2); 341 | } 342 | } 343 | return null; 344 | })(); 345 | 346 | // as per M. Miller advice 347 | features.IS_STRICT_MODE_SUPPORTED = (features.__IS_STRICT_MODE_SUPPORTED = function(){ 348 | "use strict"; 349 | return !this; 350 | })(); 351 | 352 | features.IS_ACTIVEX_ENABLED = (features.__IS_ACTIVEX_ENABLED = function(){ 353 | if (typeof ActiveXObject == 'undefined') return null; 354 | var xmlVersions = [ 355 | 'Microsoft.XMLHTTP', 356 | 'Msxml2.XMLHTTP.3.0', 357 | 'Msxml2.XMLHTTP.4.0', 358 | 'Msxml2.XMLHTTP.5.0', 359 | 'Msxml2.XMLHTTP.6.0' 360 | ]; 361 | for (var i = xmlVersions.length; i--; ) { 362 | try { 363 | if (new ActiveXObject(xmlVersions[i])) { 364 | return true; 365 | } 366 | } 367 | catch(ex) { } 368 | } 369 | return false; 370 | })(); 371 | 372 | features.IS_MATCHESSELECTOR_SUPPORTED = (features.__IS_MATCHESSELECTOR_SUPPORTED = function(){ 373 | var docEl = document.documentElement, prefixes = 'Khtml O Ms Webkit Moz'.split(' '), method = 'MatchesSelector'; 374 | if (docEl) { 375 | for (var i = prefixes.length; i--; ) { 376 | if (docEl[prefixes[i] + method]) { 377 | return true; 378 | } 379 | if (docEl[prefixes[i].toLowerCase() + method]) { 380 | return true; 381 | } 382 | } 383 | return docEl[method.charAt(0).toLowerCase() + method.slice(1)]; 384 | } 385 | return null; 386 | })(); 387 | 388 | // BUGGIES 389 | 390 | // Safari returns "function" as typeof HTMLCollection 391 | // test for typeof DOM1 `document.forms` (if exists) 392 | bugs.TYPEOF_NODELIST_IS_FUNCTION = (bugs.__TYPEOF_NODELIST_IS_FUNCTION = function(){ 393 | if (document.forms) { 394 | return (typeof document.forms == 'function'); 395 | } 396 | return null; 397 | })(); 398 | 399 | // IE returns comment nodes as part of `getElementsByTagName` results 400 | bugs.GETELEMENTSBYTAGNAME_RETURNS_COMMENT_NODES = (bugs.__GETELEMENTSBYTAGNAME_RETURNS_COMMENT_NODES = function(){ 401 | if (document.createElement) { 402 | var el = document.createElement('div'); 403 | if (el && el.getElementsByTagName) { 404 | el.innerHTML = 'a'; 405 | var all = el.getElementsByTagName('*'); 406 | // IE5.5 returns a 0-length collection when calling getElementsByTagName with wildcard 407 | if (all.length) { 408 | var lastNode = el.getElementsByTagName('*')[1]; 409 | var buggy = !!(lastNode && lastNode.nodeType === 8); 410 | return buggy; 411 | } 412 | } 413 | } 414 | return null; 415 | })(); 416 | 417 | // name attribute can not be set at run time in IE 418 | // http://msdn.microsoft.com/en-us/library/ms536389.aspx 419 | bugs.SETATTRIBUTE_IGNORES_NAME_ATTRIBUTE = (bugs.__SETATTRIBUTE_IGNORES_NAME_ATTRIBUTE = function(){ 420 | if (document.createElement) { 421 | var elForm = document.createElement('form'), 422 | elInput = document.createElement('input'), 423 | root = document.documentElement; 424 | if (elForm && 425 | elInput && 426 | elInput.setAttribute && 427 | elForm.appendChild && 428 | root && 429 | root.appendChild && 430 | root.removeChild) { 431 | elInput.setAttribute('name', 'test'); 432 | elForm.appendChild(elInput); 433 | // Older Safari (e.g. 2.0.2) populates "elements" collection only when form is within a document 434 | root.appendChild(elForm); 435 | var isBuggy = elForm.elements ? (typeof elForm.elements['test'] == 'undefined') : null; 436 | root.removeChild(elForm); 437 | return isBuggy; 438 | } 439 | } 440 | return null; 441 | })(); 442 | 443 | bugs.ELEMENT_PROPERTIES_ARE_ATTRIBUTES = (bugs.__ELEMENT_PROPERTIES_ARE_ATTRIBUTES = function(){ 444 | if (document.createElement) { 445 | var el = document.createElement('div'); 446 | if (el && el.getAttribute) { 447 | el.__foo = 'bar'; 448 | var buggy = (el.getAttribute('__foo') === 'bar'); 449 | el = null; 450 | return buggy; 451 | } 452 | } 453 | return null; 454 | })(); 455 | 456 | bugs.STRING_PROTOTYPE_REPLACE_IGNORES_FUNCTIONS = (bugs.__STRING_PROTOTYPE_REPLACE_IGNORES_FUNCTIONS = function(){ 457 | var s = 'a'; 458 | if (typeof s.replace == 'function') { 459 | return (s.replace(s, function(){ return '' }).length !== 0); 460 | } 461 | return null; 462 | })(); 463 | 464 | bugs.ARRAY_PROTOTYPE_CONCAT_ARGUMENTS_BUGGY = (bugs.__ARRAY_PROTOTYPE_CONCAT_ARGUMENTS_BUGGY = function(){ 465 | return (function(){ 466 | if (arguments instanceof Array) { 467 | return [].concat(arguments)[0] !== 1; 468 | } 469 | return null; 470 | })(1,2); 471 | })(); 472 | 473 | bugs.PROPERTIES_SHADOWING_DONTENUM_ARE_ENUMERABLE = (bugs.__PROPERTIES_SHADOWING_DONTENUM_ARE_ENUMERABLE = function(){ 474 | for (var prop in { toString: true }) { 475 | return false; 476 | } 477 | return true; 478 | })(); 479 | 480 | bugs.IS_REGEXP_WHITESPACE_CHARACTER_CLASS_BUGGY = (bugs.__IS_REGEXP_WHITESPACE_CHARACTER_CLASS_BUGGY = function(){ 481 | var str = "\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u180E\u2000\u2001\u2002"+ 482 | "\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028\u2029"; 483 | return !/^\s+$/.test(str); 484 | })(); 485 | 486 | bugs.IS_STRING_PROTOTYPE_SPLIT_REGEXP_BUGGY = (bugs.__IS_STRING_PROTOTYPE_SPLIT_REGEXP_BUGGY = function(){ 487 | var s = 'a_b'; 488 | if (typeof s.split == 'function') { 489 | return s.split(/(_)/).length !== 3; 490 | } 491 | return null; 492 | })(); 493 | 494 | bugs.PRE_ELEMENTS_IGNORE_NEWLINES = (bugs.__PRE_ELEMENTS_IGNORE_NEWLINES = function(){ 495 | if (document.createElement && document.createTextNode) { 496 | var el = document.createElement('pre'); 497 | var txt = document.createTextNode('xx'); 498 | var root = document.documentElement; 499 | if (el && 500 | el.appendChild && 501 | txt && 502 | root && 503 | root.appendChild && 504 | root.removeChild) { 505 | el.appendChild(txt); 506 | root.appendChild(el); 507 | var initialHeight = el.offsetHeight; 508 | el.firstChild.nodeValue = 'x\nx'; 509 | // check if `offsetHeight` changed after adding '\n' to the value 510 | var isIgnored = (el.offsetHeight === initialHeight); 511 | root.removeChild(el); 512 | el = txt = null; 513 | return isIgnored; 514 | } 515 | } 516 | return null; 517 | })(); 518 | 519 | bugs.SELECT_ELEMENT_INNERHTML_BUGGY = (bugs.__SELECT_ELEMENT_INNERHTML_BUGGY = function(){ 520 | if (document.createElement) { 521 | var el = document.createElement('select'), 522 | isBuggy = true; 523 | if (el) { 524 | el.innerHTML = '

'+ 624 | '
'+ 625 | '
<\/div>'+ 626 | '
x<\/div>'+ 627 | '<\/div>'+ 628 | '<\/div>'; 629 | var wrapper = document.createElement('div'); 630 | if (wrapper) { 631 | wrapper.innerHTML = payload; 632 | body.insertBefore(wrapper, body.firstChild); 633 | var el = document.getElementById(id); 634 | if (el && el.style) { 635 | if (el.offsetTop !== 10) { 636 | // buggy, set position to relative and check if it fixes it 637 | el.style.position = 'relative'; 638 | if (el.offsetTop === 10) { 639 | isBuggy = true; 640 | } 641 | } 642 | else { 643 | isBuggy = false; 644 | } 645 | } 646 | body.removeChild(wrapper); 647 | } 648 | wrapper = null; 649 | } 650 | return isBuggy; 651 | })(); 652 | 653 | bugs.IS_XPATH_POSITION_FUNCTION_BUGGY = ( 654 | bugs.__IS_XPATH_POSITION_FUNCTION_BUGGY = function(){ 655 | var isBuggy = null; 656 | if (document.evaluate && window.XPathResult) { 657 | var el = document.createElement('div'); 658 | el.innerHTML = '

a

b

' 659 | var xpath = ".//*[local-name()='p' or local-name()='P'][position() = 2]"; 660 | var result = document.evaluate(xpath, el, null, 661 | XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); 662 | if (result) { 663 | // TODO: investigate further 664 | // alert(result.snapshotItem(0)); 665 | } 666 | } 667 | return isBuggy; 668 | })(); 669 | 670 | bugs.IS_DOCUMENT_GETELEMENTSBYNAME_BUGGY = ( 671 | bugs.__IS_DOCUMENT_GETELEMENTSBYNAME_BUGGY = function(){ 672 | var isBuggy = null, 673 | docEl = document.documentElement; 674 | if (docEl && 675 | docEl.appendChild && 676 | docEl.removeChild && 677 | document.getElementsByName && 678 | document.createElement) { 679 | var el = document.createElement('div'); 680 | if (el) { 681 | var uid = 'x' + (Math.random() + '').slice(2); 682 | el.id = uid; 683 | docEl.appendChild(el); 684 | isBuggy = document.getElementsByName(uid)[0] === el; 685 | docEl.removeChild(el); 686 | } 687 | } 688 | 689 | return isBuggy; 690 | })(); 691 | 692 | bugs.NAMED_FUNCTION_EXPRESSION_IDENTIFIER_LEAKS_ONTO_ENCLOSING_SCOPE = ( 693 | bugs.__NAMED_FUNCTION_EXPRESSION_IDENTIFIER_LEAKS_ONTO_ENCLOSING_SCOPE = function(){ 694 | // make sure `g` is not found higher up the scope chain, by declaring it here 695 | var g = null; 696 | return (function(){ 697 | var f = function g(){}; 698 | // `g` should be resolved to `null` (the one we declared outside this function) 699 | // but since named function expression identifier leaks onto the enclosing scope in IE, 700 | // it will be resolved to a function 701 | return (typeof g == 'function'); 702 | })(); 703 | })(); 704 | 705 | bugs.ARGUMENTS_INSTANCEOF_ARRAY = (bugs.__ARGUMENTS_INSTANCEOF_ARRAY = function(){ 706 | return arguments instanceof Array; 707 | })(); 708 | 709 | bugs.IS_OVERFLOW_STYLE_BUGGY = (bugs.__IS_OVERFLOW_STYLE_BUGGY = function(){ 710 | var isBuggy = null; 711 | if (document.createElement) { 712 | var el = document.createElement('div'); 713 | el.innerHTML = '

x

'; 714 | var firstChild = el.firstChild; 715 | if (firstChild && firstChild.style) { 716 | firstChild.style.overflow = 'hidden'; 717 | isBuggy = firstChild.style.overflow !== 'hidden'; 718 | } 719 | el = firstChild = null; 720 | } 721 | return isBuggy; 722 | })(); 723 | 724 | bugs.IS_QUERY_SELECTOR_ALL_BUGGY = (bugs.__IS_QUERY_SELECTOR_ALL_BUGGY = function(){ 725 | var isBuggy = null; 726 | if (document.createElement) { 727 | var el = document.createElement('div'); 728 | if (el && el.querySelectorAll) { 729 | el.innerHTML = ''; 730 | isBuggy = el.querySelectorAll("param").length != 1; 731 | } 732 | el = null; 733 | } 734 | return isBuggy; 735 | })(); 736 | 737 | __global.__totalTime = (new Date() - t); 738 | 739 | __global.__features = features; 740 | __global.__bugs = bugs; 741 | 742 | })(this); -------------------------------------------------------------------------------- /getStyleProperty.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @method getStyleProperty 3 | * @param {String} propName style property to test 4 | * @param {HTMLElement} element optional optional element to test 5 | * @return {String | undefined} 6 | * 7 | * @example getStyleProperty('borderRadius'); 8 | */ 9 | var getStyleProperty = (function(){ 10 | 11 | var prefixes = ['Moz', 'Webkit', 'Khtml', 'O', 'Ms']; 12 | 13 | function getStyleProperty(propName, element) { 14 | element = element || document.documentElement; 15 | var style = element.style, 16 | prefixed; 17 | 18 | // test standard property first 19 | if (typeof style[propName] == 'string') return propName; 20 | 21 | // capitalize 22 | propName = propName.charAt(0).toUpperCase() + propName.slice(1); 23 | 24 | // test vendor specific properties 25 | for (var i=0, l=prefixes.length; i 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Javascript feature tests 13 | 14 | 15 | 16 | 17 | 18 |

Common feature tests:

19 |
20 |

21 | Too many times I see Javascript authors fall into the trap of browser sniffing. 22 | Feature detection is known to be a much better practice when it comes to creating robust scripts for general web. 23 | Hopefully, this collection can serve as a repository of most common (non-trivial) feature tests. 24 | Most of the tests require no presence of document.body and so can be run at any time. 25 | Otherwise, a note about document.body requirement is mentioned in test description. 26 | All of the tests assume the presence of global document property. Everything else is tested before it's being used. 27 |

28 |

29 | For CSS support detection see getStyleProperty and tests.
30 | For event support detection, see isEventSupported and tests. 31 |

32 |

33 | Tested browsers: 34 | Interner Explorer 5.5 - 8, 35 | Firefox 1.5.0.12 - 3.5, 36 | Opera 7.54 - 10.10, 37 | Safari 2.0 - 4.0.4, 38 | Mobile Safari 3.2, 39 | Chrome 0.4.154.29 - 3.0.192.0, 40 | Camino 1.6.6 - 1.6.8, 41 | Konqueror 4.2.2, 42 | BlackBerry 9000 (Bold), 9530 (Storm) 43 |

44 |

45 | Found an error or have a suggestion? Please, tell me about it 46 |

47 |

Source and history are available on GitHub

48 |
49 | 50 | Last updated: May 29, 2011 51 | 52 |

Features:

53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 262 | 263 | 264 | 265 | 266 | 276 | 277 | 278 | 279 | 280 | 281 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 306 | 307 | 308 | 309 | 310 | 311 |
NameValueAffected BrowsersInfo 61 | Test code
62 | 63 | Show all / 64 | Hide all 65 | 66 |
IS_ELEMENT_TAGNAME_UPPERCASED- 75 | 76 | element.tagName on MDC 77 | 78 | show
ARRAY_PROTOTYPE_SLICE_CAN_CONVERT_NODELIST_TO_ARRAYnon-IEshow
WINDOW_EVAL_EVALUATES_IN_GLOBAL_SCOPEFirefox, Safari 3.2.1, OperaTest whether window.eval evaluates string in global scopeshow
IS_EVENT_METAKEY_PRESENTshow
IS_EVENT_RELATEDTARGET_PRESENT 115 | 116 | MouseEvent::relatedTarget 117 | 118 | show
IS_EVENT_PREVENTDEFAULT_PRESENTnon-IE 128 | Test whether DOM L2 Event::preventDefault method is present on event objectshow
IS_EVENT_SRCELEMENT_PRESENTIE, Opera, Chrome, Safari 2+ 138 | Test whether Event::srcElement is present on event object 139 | show
IS_NATIVE_HAS_ATTRIBUTE_PRESENT- 148 | Check for 149 | actual 150 | presence of Element::hasAttribute. 151 | The test should return proper result even if method was defined/removed by 3rd party scripts 152 | show
IS_POSITION_FIXED_SUPPORTED- 161 | Check if position:fixed; is supported. Requires presence of document.body 162 | show
IS_CONTEXTMENU_EVENT_SUPPORTEDAll except Opera <=10.a and Konqueror (4.2.2) 171 | Check if "contextmenu" event is supported. 172 | show
COMPUTED_STYLE_RETURNS_VALUES_FOR_STATICLY_POSITIONED_ELEMENTSOpera 8.54+ 181 | Computed style returns actual values for statically positioned elements, instead of 'auto' 182 | show
IS_RGBA_SUPPORTEDFF3+, Safari 3+ 192 | Check whether RGBA is supported 193 | show
IS_CSS_BORDER_RADIUS_SUPPORTEDWebKit-, Gecko- and KHTML- (newer) based 203 | Check whether CSS3 border-radius property is supported (or any of vendor proprietary extensions - "-moz-border-radius", "-webkit-border-radius", "-khtml-border-radius") 204 | show
IS_CSS_TRANSFORMATION_SUPPORTEDChrome, Firefox 3.1+ 214 | 215 | CSSTransforms specs 216 | 217 | show
IS_CANVAS_SUPPORTEDMost of non-IE, Opera 9.x+ 227 | Is HTML5 canvas element supported? 228 | show
ELEMENT_CHILDREN_RETURNS_ELEMENT_NODESSafari 2.x is buggy; IE returns comment nodes; Firefox <3.5 doesn't support `children` at all 238 | Is Element::children supported? If so, does it return element nodes only? 239 | show
IS_CSS_ENABLED 249 | Checks whether CSS is enabled. Requires presence of document.body. Note that certain "!important" declarations might cause this test to produce false positives. 250 | show
IS_ACTIVEX_ENABLED 260 | Checks if ActiveX controls are enabled (if available) 261 | show
IS_QUIRKS_MODE 282 | Checks if browser is in quirks mode. Based on technique by Google doctype. 283 | show
IS_STRICT_MODE_SUPPORTED 293 | Checks if ES5 strict mode is supported. 294 | show
IS_MATCHESSELECTOR_SUPPORTEDFirefox 3.6+; WebKit (r48723+) 304 | Checks if Element::matchesSelector is supported. 305 | show
312 | 313 |

Bugs:

314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 432 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 536 | 537 | 538 | 539 | 540 | 541 |
NameValueAffected BrowsersInfoTest code
GETELEMENTSBYTAGNAME_RETURNS_COMMENT_NODESIETest whether getElementsByTagName returns comments nodesshow
IS_REGEXP_WHITESPACE_CHARACTER_CLASS_BUGGYIE, Safari, Konqueror 4.2.2 339 | Test whether /\s/ matches whitespace characters (\t,\v,\f, etc. including unicode ones). 340 | See 15.10.2.12 and 341 | whitespace deviations article. 342 | show
IS_STRING_PROTOTYPE_SPLIT_REGEXP_BUGGYIE, Konqueror 4.2.2 351 | Test whether String.prototype.split works with regexp capturing groups properly 352 | show
SETATTRIBUTE_IGNORES_NAME_ATTRIBUTEIE <=7 361 | name attribute can not be changed at runtime in IE, see 362 | article on MSDN 363 | show
ELEMENT_PROPERTIES_ARE_ATTRIBUTESIEshow
QUERY_SELECTOR_IGNORES_CAPITALIZED_VALUESWebKit bug #19047show
STRING_PROTOTYPE_REPLACE_IGNORES_FUNCTIONSSafari <=2.0.2 388 | WebKit bug #3294 389 | show
ARRAY_PROTOTYPE_CONCAT_ARGUMENTS_BUGGYOpera <9.5show
PROPERTIES_SHADOWING_DONTENUM_ARE_ENUMERABLEIESee JScript DontEnum bug description on MDCshow
PRE_ELEMENTS_IGNORE_NEWLINESIE<=7, Opera 8.54+show
SELECT_ELEMENT_INNERHTML_BUGGYIEBug description on MSDNshow
TABLE_ELEMENT_INNERHTML_BUGGYIE, Konqueror 4.2.2 430 | innerHTML article on MSDN 431 | 433 | show 434 |
SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDINGIEshow
DOCUMENT_GETELEMENTBYID_CONFUSES_IDS_WITH_NAMESIE <=7, Opera <9.5document.getElementById returns elements with corresponding name attributesshow
DOCUMENT_GETELEMENTBYID_IGNORES_CASEIE <=7show
BUGGY_OFFSET_VALUES_FOR_STATIC_ELEMENTS_INSIDE_POSITIONED_ONESIE <=7, Opera <8.54Requires presence of document.bodyshow
IS_XPATH_POSITION_FUNCTION_BUGGYSafari 3+, Opera 9.64+show
IS_DOCUMENT_GETELEMENTSBYNAME_BUGGYIE6+, Opera 8.54 (and possibly earlier) - 10aTest whether document.getElementsByName returns elements with same-named ids. See MSDN article.show
NAMED_FUNCTION_EXPRESSION_IDENTIFIER_LEAKS_ONTO_ENCLOSING_SCOPEIEshow
ARGUMENTS_INSTANCEOF_ARRAYOpera 501 | Opera bug #326 (Unofficial) 502 | show
IS_OVERFLOW_STYLE_BUGGYKonqueror 4.2.2 (possibly earlier too) 512 | Konqueror (at least 4.2.2) fails to change element's style.overflow if its value was set from within HTML. 513 | show
IS_CONTAINS_BUGGYSafari 2.0 - 2.0.4 (possibly earlier too) 523 | In older Safari (non-standard) Element::contains returns true for any unrelated element, as long as that element is not an ancestor of tested one. 524 | show
IS_QUERY_SELECTOR_ALL_BUGGYIE8 534 | querySelectorAll fails to match param element contained within object one 535 | show
542 | 543 | 544 | 545 | 546 | 550 | 551 | 554 | 555 | 556 | -------------------------------------------------------------------------------- /master.css: -------------------------------------------------------------------------------- 1 | body { font-family: Optima, Arial, serif; } 2 | table { text-align: left; border-collapse: collapse; } 3 | th { background-color: #555; color: #eee; padding: 5px 10px; font-weight: normal; border: 1px solid #eee; } 4 | td.name { text-align: right; padding-right: 10px; width: 400px; } 5 | td.true { background-color: #5f5; } 6 | td.false { background-color: #f55; } 7 | td.na { background-color: yellow; } 8 | td { border: 1px solid #aaa; padding: 5px; vertical-align: top; } 9 | dl { margin-left: 1em; } 10 | h1 { background: #fee; } 11 | #last-updated { background: #ffc; border-bottom: 1px dotted #aaa; padding: 0 3px; font-size: 13px; } 12 | .test-code { background: #fff; } 13 | tr { background: #eee; } 14 | th.name { padding: 0 10px; } 15 | th a { font-size: 11px; color: #eef; } 16 | th.code { width: 100px; } 17 | pre { width: 1024px; overflow: auto; } 18 | abbr { border: none; } -------------------------------------------------------------------------------- /master.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 3 | if (!('getElementsByClassName' in document)) { 4 | document.getElementsByClassName = function(className) { 5 | var all = document.getElementsByTagName('*'); 6 | if (all.length === 0 && 'all' in document) { 7 | // IE5.5 8 | all = document.all; 9 | } 10 | if (!className) return all; 11 | var re = new RegExp('(^|\\s)' + className + '(\\s|$)'); 12 | var result = []; 13 | for (var i=0, len=all.length; i -1) { 111 | toggleCodeSection(target); 112 | } 113 | else if ((' ' + target.className + ' ').indexOf(' show-all ') > -1) { 114 | var all = document.getElementsByClassName('show-test-code'); 115 | for (var i=0, len=all.length; i -1) { 120 | var all = document.getElementsByClassName('show-test-code'); 121 | for (var i=0, len=all.length; i' + __totalTime + 'ms'); 143 | document.body.appendChild(p); 144 | } 145 | 146 | var hash = document.location.hash.slice(1); 147 | var links = document.anchors, i = links.length; 148 | while (i--) { 149 | if (links[i].name === hash) { 150 | toggleCodeSection(links[i]); 151 | break; 152 | } 153 | } 154 | })(); -------------------------------------------------------------------------------- /style.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | Style tests 9 | 10 | 18 | 19 | 20 | 21 | 22 |

See explanation of getStyleProperty

23 | 95 |

by kangax

96 | 97 | --------------------------------------------------------------------------------