forked from Modernizr/Modernizr
-
Notifications
You must be signed in to change notification settings - Fork 0
/
modernizr.js
1146 lines (910 loc) · 43.6 KB
/
modernizr.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
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
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
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
430
431
432
433
434
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
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*!
* Modernizr v2.1pre
* www.modernizr.com
*
* Copyright (c) 2009-2011 Faruk Ates, Paul Irish, Alex Sexton
* Dual-licensed under the BSD or MIT licenses: www.modernizr.com/license/
*/
/*
* Modernizr tests which native CSS3 and HTML5 features are available in
* the current UA and makes the results available to you in two ways:
* as properties on a global Modernizr object, and as classes on the
* <html> element. This information allows you to progressively enhance
* your pages with a granular level of control over the experience.
*
* Modernizr has an optional (not included) conditional resource loader
* called Modernizr.load(), based on Yepnope.
* To get a build that includes Modernizr.load(), as well as choosing
* which tests to include, go to www.modernizr.com/download/
*
* Authors Faruk Ates, Paul Irish, Alex Sexton,
* Contributors Ryan Seddon, Ben Alman
*/
window.Modernizr = (function( window, document, undefined ) {
var version = '2.1pre',
Modernizr = {},
// option for enabling the HTML classes to be added
enableClasses = true,
docElement = document.documentElement,
docHead = document.head || document.getElementsByTagName('head')[0],
/**
* Create our "modernizr" element that we do most feature tests on.
*/
mod = 'modernizr',
modElem = document.createElement(mod),
mStyle = modElem.style,
/**
* Create the input element for various Web Forms feature tests.
*/
inputElem = document.createElement('input'),
smile = ':)',
toString = {}.toString,
// List of property values to set for css tests. See ticket #21
prefixes = ' -webkit- -moz- -o- -ms- -khtml- '.split(' '),
// Following spec is to expose vendor-specific style properties as:
// elem.style.WebkitBorderRadius
// and the following would be incorrect:
// elem.style.webkitBorderRadius
// Webkit ghosts their properties in lowercase but Opera & Moz do not.
// Microsoft uses a lowercase `ms` instead of the correct `Ms` in IE8+
// erik.eae.net/archives/2008/03/10/21.48.10/
// More here: github.com/Modernizr/Modernizr/issues/issue/21
omPrefixes = 'Webkit Moz O ms Khtml',
cssomPrefixes = omPrefixes.split(' '),
domPrefixes = omPrefixes.toLowerCase().split(' '),
ns = {'svg': 'http://www.w3.org/2000/svg'},
tests = {},
inputs = {},
attrs = {},
classes = [],
featureName, // used in testing loop
// Inject element with style element and some CSS rules
injectElementWithStyles = function( rule, callback, nodes, testnames ) {
var style, ret, node,
div = document.createElement('div'),
// After page load injecting a fake body doesn't work so check if body exists
body = document.body,
// IE6 and 7 won't return offsetWidth or offsetHeight unless it's in the body element, so we fake it.
fakeBody = body ? body : document.createElement('body');
if ( parseInt(nodes, 10) ) {
// In order not to give false positives we create a node for each test
// This also allows the method to scale for unspecified uses
while ( nodes-- ) {
node = document.createElement('div');
node.id = testnames ? testnames[nodes] : mod + (nodes + 1);
div.appendChild(node);
}
}
// <style> elements in IE6-9 are considered 'NoScope' elements and therefore will be removed
// when injected with innerHTML. To get around this you need to prepend the 'NoScope' element
// with a 'scoped' element, in our case the soft-hyphen entity as it won't mess with our measurements.
// msdn.microsoft.com/en-us/library/ms533897%28VS.85%29.aspx
// Documents served as xml will throw if using ­ so use xml friendly encoded version. See issue #277
style = ['­','<style>', rule, '</style>'].join('');
div.id = mod;
// IE6 will false positive on some tests due to the style element inside the test div somehow interfering offsetHeight, so insert it into body or fakebody.
// Opera will act all quirky when injecting elements in documentElement when page is served as xml, needs fakebody too. #270
fakeBody.innerHTML += style;
fakeBody.appendChild(div);
docElement.appendChild(fakeBody);
ret = callback(div, rule);
// If this is done after page load we don't want to remove the body so check if body exists
!body ? fakeBody.parentNode.removeChild(fakeBody) : div.parentNode.removeChild(div);
return !!ret;
},
// adapted from matchMedia polyfill
// by Scott Jehl and Paul Irish
// gist.github.com/786768
testMediaQuery = function( mq ) {
var matchMedia = window.matchMedia || window.msMatchMedia;
if ( matchMedia ) {
return matchMedia(mq).matches;
}
var bool;
injectElementWithStyles('@media ' + mq + ' { #' + mod + ' { position: absolute; } }', function( node ) {
bool = (window.getComputedStyle ?
getComputedStyle(node, null) :
node.currentStyle)['position'] == 'absolute';
});
return bool;
},
/**
* isEventSupported determines if a given element supports the given event
* function from yura.thinkweb2.com/isEventSupported/
*/
isEventSupported = (function() {
var TAGNAMES = {
'select': 'input', 'change': 'input',
'submit': 'form', 'reset': 'form',
'error': 'img', 'load': 'img', 'abort': 'img'
};
function isEventSupported( eventName, element ) {
element = element || document.createElement(TAGNAMES[eventName] || 'div');
eventName = 'on' + eventName;
// When using `setAttribute`, IE skips "unload", WebKit skips "unload" and "resize", whereas `in` "catches" those
var isSupported = eventName in element;
if ( !isSupported ) {
// If it has no `setAttribute` (i.e. doesn't implement Node interface), try generic element
if ( !element.setAttribute ) {
element = document.createElement('div');
}
if ( element.setAttribute && element.removeAttribute ) {
element.setAttribute(eventName, '');
isSupported = is(element[eventName], 'function');
// If property was created, "remove it" (by setting value to `undefined`)
if ( !is(element[eventName], 'undefined') ) {
element[eventName] = undefined;
}
element.removeAttribute(eventName);
}
}
element = null;
return isSupported;
}
return isEventSupported;
})();
// hasOwnProperty shim by kangax needed for Safari 2.0 support
var _hasOwnProperty = ({}).hasOwnProperty, hasOwnProperty;
if ( !is(_hasOwnProperty, 'undefined') && !is(_hasOwnProperty.call, 'undefined') ) {
hasOwnProperty = function (object, property) {
return _hasOwnProperty.call(object, property);
};
}
else {
hasOwnProperty = function (object, property) { /* yes, this can give false positives/negatives, but most of the time we don't care about those */
return ((property in object) && is(object.constructor.prototype[property], 'undefined'));
};
}
/**
* setCss applies given styles to the Modernizr DOM node.
*/
function setCss( str ) {
mStyle.cssText = str;
}
/**
* setCssAll extrapolates all vendor-specific css strings.
*/
function setCssAll( str1, str2 ) {
return setCss(prefixes.join(str1 + ';') + ( str2 || '' ));
}
/**
* is returns a boolean for if typeof obj is exactly type.
*/
function is( obj, type ) {
return typeof obj === type;
}
/**
* contains returns a boolean for if substr is found within str.
*/
function contains( str, substr ) {
return !!~('' + str).indexOf(substr);
}
/**
* testProps is a generic CSS / DOM property test; if a browser supports
* a certain property, it won't return undefined for it.
* A supported CSS property returns empty string when its not yet set.
*/
function testProps( props, prefixed ) {
for ( var i in props ) {
if ( mStyle[ props[i] ] !== undefined ) {
return prefixed == 'pfx' ? props[i] : true;
}
}
return false;
}
/**
* testDOMProps is a generic DOM property test; if a browser supports
* a certain property, it won't return undefined for it.
*/
function testDOMProps( props, obj ) {
for ( var i in props ) {
if ( obj[ props[i] ] !== undefined) {
return props[i];
}
}
return false;
}
/**
* testPropsAll tests a list of DOM properties we want to check against.
* We specify literally ALL possible (known and/or likely) properties on
* the element including the non-vendor prefixed one, for forward-
* compatibility.
*/
function testPropsAll( prop, prefixed ) {
var ucProp = prop.charAt(0).toUpperCase() + prop.substr(1),
props = (prop + ' ' + cssomPrefixes.join(ucProp + ' ') + ucProp).split(' ');
if(!is(prefixed, "object")) {
return testProps(props, prefixed);
} else {
props = (prop + ' ' + (domPrefixes).join(ucProp + ' ') + ucProp).split(' ');
return testDOMProps(props, prefixed);
}
}
/**
* testBundle tests a list of CSS features that require element and style injection.
* By bundling them together we can reduce the need to touch the DOM multiple times.
*/
/*>>testBundle*/
var testBundle = (function( styles, tests ) {
var style = styles.join(''),
len = tests.length;
injectElementWithStyles(style, function( node, rule ) {
var style = document.styleSheets[document.styleSheets.length - 1],
// IE8 will bork if you create a custom build that excludes both fontface and generatedcontent tests.
// So we check for cssRules and that there is a rule available
// More here: github.com/Modernizr/Modernizr/issues/288 & github.com/Modernizr/Modernizr/issues/293
cssText = style ? (style.cssRules && style.cssRules[0] ? style.cssRules[0].cssText : style.cssText || '') : '',
children = node.childNodes, hash = {};
while ( len-- ) {
hash[children[len].id] = children[len];
}
/*>>touch*/ Modernizr['touch'] = ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch || (hash['touch'] && hash['touch'].offsetTop) === 9; /*>>touch*/
/*>>csstransforms3d*/ Modernizr['csstransforms3d'] = (hash['csstransforms3d'] && hash['csstransforms3d'].offsetLeft) === 9; /*>>csstransforms3d*/
/*>>generatedcontent*/Modernizr['generatedcontent'] = (hash['generatedcontent'] && hash['generatedcontent'].offsetHeight) >= 1; /*>>generatedcontent*/
/*>>fontface*/ Modernizr['fontface'] = /src/i.test(cssText) &&
cssText.indexOf(rule.split(' ')[0]) === 0; /*>>fontface*/
}, len, tests);
})([
// Pass in styles to be injected into document
/*>>fontface*/ '@font-face {font-family:"font";src:url("https://")}' /*>>fontface*/
/*>>touch*/ ,['@media (',prefixes.join('touch-enabled),('),mod,')',
'{#touch{top:9px;position:absolute}}'].join('') /*>>touch*/
/*>>csstransforms3d*/ ,['@media (',prefixes.join('transform-3d),('),mod,')',
'{#csstransforms3d{left:9px;position:absolute}}'].join('')/*>>csstransforms3d*/
/*>>generatedcontent*/,['#generatedcontent:after{content:"',smile,'";visibility:hidden}'].join('') /*>>generatedcontent*/
],
[
/*>>fontface*/ 'fontface' /*>>fontface*/
/*>>touch*/ ,'touch' /*>>touch*/
/*>>csstransforms3d*/ ,'csstransforms3d' /*>>csstransforms3d*/
/*>>generatedcontent*/,'generatedcontent' /*>>generatedcontent*/
]);/*>>testBundle*/
/**
* Tests
* -----
*/
// The *new* flexbox
// dev.w3.org/csswg/css3-flexbox
tests['flexbox'] = function() {
return testPropsAll('flexOrder');
};
// The *old* flexbox
// www.w3.org/TR/2009/WD-css3-flexbox-20090723/
tests['flexbox-legacy'] = function() {
return testPropsAll('boxDirection');
};
// On the S60 and BB Storm, getContext exists, but always returns undefined
// so we actually have to call getContext() to verify
// github.com/Modernizr/Modernizr/issues/issue/97/
tests['canvas'] = function() {
var elem = document.createElement('canvas');
return !!(elem.getContext && elem.getContext('2d'));
};
tests['canvastext'] = function() {
return !!(Modernizr['canvas'] && is(document.createElement('canvas').getContext('2d').fillText, 'function'));
};
// this test initiates a new webgl context.
// webk.it/70117 is tracking a legit feature detect proposal
tests['webgl'] = function() {
try {
var canvas = document.createElement('canvas'),
ret;
ret = !!(window.WebGLRenderingContext && (canvas.getContext('experimental-webgl') || canvas.getContext('webgl')));
canvas = undefined;
} catch (e){
ret = false;
}
return ret;
};
/*
* The Modernizr.touch test only indicates if the browser supports
* touch events, which does not necessarily reflect a touchscreen
* device, as evidenced by tablets running Windows 7 or, alas,
* the Palm Pre / WebOS (touch) phones.
*
* Additionally, Chrome (desktop) used to lie about its support on this,
* but that has since been rectified: crbug.com/36415
*
* We also test for Firefox 4 Multitouch Support.
*
* For more info, see: modernizr.github.com/Modernizr/touch.html
*/
tests['touch'] = function() {
return Modernizr['touch'];
};
/**
* geolocation tests for the new Geolocation API specification.
* This test is a standards compliant-only test; for more complete
* testing, including a Google Gears fallback, please see:
* code.google.com/p/geo-location-javascript/
* or view a fallback solution using google's geo API:
* gist.github.com/366184
*/
tests['geolocation'] = function() {
return !!navigator.geolocation;
};
// Per 1.6:
// This used to be Modernizr.crosswindowmessaging but the longer
// name has been deprecated in favor of a shorter and property-matching one.
// The old API is still available in 1.6, but as of 2.0 will throw a warning,
// and in the first release thereafter disappear entirely.
tests['postmessage'] = function() {
return !!window.postMessage;
};
// Chrome incognito mode used to throw an exception when using openDatabase
// It doesn't anymore.
tests['websqldatabase'] = function() {
return !!window.openDatabase;
};
// Vendors had inconsistent prefixing with the experimental Indexed DB:
// - Webkit's implementation is accessible through webkitIndexedDB
// - Firefox shipped moz_indexedDB before FF4b9, but since then has been mozIndexedDB
// For speed, we don't test the legacy (and beta-only) indexedDB
tests['indexedDB'] = function() {
return !!testPropsAll("indexedDB",window);
};
// documentMode logic from YUI to filter out IE8 Compat Mode
// which false positives.
tests['hashchange'] = function() {
return isEventSupported('hashchange', window) && (document.documentMode === undefined || document.documentMode > 7);
};
// Per 1.6:
// This used to be Modernizr.historymanagement but the longer
// name has been deprecated in favor of a shorter and property-matching one.
// The old API is still available in 1.6, but as of 2.0 will throw a warning,
// and in the first release thereafter disappear entirely.
tests['history'] = function() {
return !!(window.history && history.pushState);
};
tests['draganddrop'] = function() {
var div = document.createElement('div');
return ('draggable' in div) || ('ondragstart' in div && 'ondrop' in div);
};
// FIXME: Once FF10 is sunsetted, we can drop prefixed MozWebSocket
// bugzil.la/695635
tests['websockets'] = function() {
for ( var i = -1, len = cssomPrefixes.length; ++i < len; ){
if ( window[cssomPrefixes[i] + 'WebSocket'] ){
return true;
}
}
return 'WebSocket' in window;
};
// css-tricks.com/rgba-browser-support/
tests['rgba'] = function() {
// Set an rgba() color and check the returned value
setCss('background-color:rgba(150,255,150,.5)');
return contains(mStyle.backgroundColor, 'rgba');
};
tests['hsla'] = function() {
// Same as rgba(), in fact, browsers re-map hsla() to rgba() internally,
// except IE9 who retains it as hsla
setCss('background-color:hsla(120,40%,100%,.5)');
return contains(mStyle.backgroundColor, 'rgba') || contains(mStyle.backgroundColor, 'hsla');
};
tests['multiplebgs'] = function() {
// Setting multiple images AND a color on the background shorthand property
// and then querying the style.background property value for the number of
// occurrences of "url(" is a reliable method for detecting ACTUAL support for this!
setCss('background:url(https://),url(https://),red url(https://)');
// If the UA supports multiple backgrounds, there should be three occurrences
// of the string "url(" in the return value for elemStyle.background
return /(url\s*\(.*?){3}/.test(mStyle.background);
};
// In testing support for a given CSS property, it's legit to test:
// `elem.style[styleName] !== undefined`
// If the property is supported it will return an empty string,
// if unsupported it will return undefined.
// We'll take advantage of this quick test and skip setting a style
// on our modernizr element, but instead just testing undefined vs
// empty string.
tests['backgroundsize'] = function() {
return testPropsAll('backgroundSize');
};
tests['borderimage'] = function() {
return testPropsAll('borderImage');
};
// Super comprehensive table about all the unique implementations of
// border-radius: muddledramblings.com/table-of-css3-border-radius-compliance
tests['borderradius'] = function() {
return testPropsAll('borderRadius');
};
// WebOS unfortunately false positives on this test.
tests['boxshadow'] = function() {
return testPropsAll('boxShadow');
};
// FF3.0 will false positive on this test
tests['textshadow'] = function() {
return document.createElement('div').style.textShadow === '';
};
tests['opacity'] = function() {
// Browsers that actually have CSS Opacity implemented have done so
// according to spec, which means their return values are within the
// range of [0.0,1.0] - including the leading zero.
setCssAll('opacity:.55');
// The non-literal . in this regex is intentional:
// German Chrome returns this value as 0,55
// github.com/Modernizr/Modernizr/issues/#issue/59/comment/516632
return /^0.55$/.test(mStyle.opacity);
};
// Note, Android < 4 will pass this test, but can only animate
// a single property at a time
// daneden.me/2011/12/putting-up-with-androids-bullshit/
tests['cssanimations'] = function() {
return testPropsAll('animationName');
};
tests['csscolumns'] = function() {
return testPropsAll('columnCount');
};
tests['cssgradients'] = function() {
/**
* For CSS Gradients syntax, please see:
* webkit.org/blog/175/introducing-css-gradients/
* developer.mozilla.org/en/CSS/-moz-linear-gradient
* developer.mozilla.org/en/CSS/-moz-radial-gradient
* dev.w3.org/csswg/css3-images/#gradients-
*/
var str1 = 'background-image:',
str2 = 'gradient(linear,left top,right bottom,from(#9f9),to(white));',
str3 = 'linear-gradient(left top,#9f9, white);';
setCss(
// legacy webkit syntax (FIXME: remove when syntax not in use anymore)
(str1 + '-webkit- '.split(' ').join(str2 + str1)
// standard syntax // trailing 'background-image:'
+ prefixes.join(str3 + str1)).slice(0, -str1.length)
);
return contains(mStyle.backgroundImage, 'gradient');
};
tests['cssreflections'] = function() {
return testPropsAll('boxReflect');
};
tests['csstransforms'] = function() {
return !!testProps(['transformProperty', 'WebkitTransform', 'MozTransform', 'OTransform', 'msTransform']);
};
tests['csstransforms3d'] = function() {
var ret = !!testProps(['perspectiveProperty', 'WebkitPerspective', 'MozPerspective', 'OPerspective', 'msPerspective']);
// Webkit's 3D transforms are passed off to the browser's own graphics renderer.
// It works fine in Safari on Leopard and Snow Leopard, but not in Chrome in
// some conditions. As a result, Webkit typically recognizes the syntax but
// will sometimes throw a false positive, thus we must do a more thorough check:
if ( ret && 'webkitPerspective' in docElement.style ) {
// Webkit allows this media query to succeed only if the feature is enabled.
// `@media (transform-3d),(-o-transform-3d),(-moz-transform-3d),(-ms-transform-3d),(-webkit-transform-3d),(modernizr){ ... }`
ret = Modernizr['csstransforms3d'];
}
return ret;
};
tests['csstransitions'] = function() {
return testPropsAll('transitionProperty');
};
/*>>fontface*/
// @font-face detection routine by Diego Perini
// javascript.nwbox.com/CSSSupport/
// false positives in WebOS: github.com/Modernizr/Modernizr/issues/342
tests['fontface'] = function() {
return Modernizr['fontface'];
};
/*>>fontface*/
// CSS generated content detection
tests['generatedcontent'] = function() {
return Modernizr['generatedcontent'];
};
// These tests evaluate support of the video/audio elements, as well as
// testing what types of content they support.
//
// We're using the Boolean constructor here, so that we can extend the value
// e.g. Modernizr.video // true
// Modernizr.video.ogg // 'probably'
//
// Codec values from : github.com/NielsLeenheer/html5test/blob/9106a8/index.html#L845
// thx to NielsLeenheer and zcorpan
// Note: in some older browsers, "no" was a return value instead of empty string.
// It was live in FF3.5.0 and 3.5.1, but fixed in 3.5.2
// It was also live in Safari 4.0.0 - 4.0.4, but fixed in 4.0.5
tests['video'] = function() {
var elem = document.createElement('video'),
bool = false;
// IE9 Running on Windows Server SKU can cause an exception to be thrown, bug #224
try {
if ( bool = !!elem.canPlayType ) {
bool = new Boolean(bool);
bool.ogg = elem.canPlayType('video/ogg; codecs="theora"') .replace(/^no$/,'');
bool.h264 = elem.canPlayType('video/mp4; codecs="avc1.42E01E"') .replace(/^no$/,'');
bool.webm = elem.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,'');
}
} catch(e) { }
return bool;
};
tests['audio'] = function() {
var elem = document.createElement('audio'),
bool = false;
try {
if ( bool = !!elem.canPlayType ) {
bool = new Boolean(bool);
bool.ogg = elem.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,'');
bool.mp3 = elem.canPlayType('audio/mpeg;') .replace(/^no$/,'');
// Mimetypes accepted:
// developer.mozilla.org/En/Media_formats_supported_by_the_audio_and_video_elements
// bit.ly/iphoneoscodecs
bool.wav = elem.canPlayType('audio/wav; codecs="1"') .replace(/^no$/,'');
bool.m4a = ( elem.canPlayType('audio/x-m4a;') ||
elem.canPlayType('audio/aac;')) .replace(/^no$/,'');
}
} catch(e) { }
return bool;
};
// In FF4, if disabled, window.localStorage should === null.
// Normally, we could not test that directly and need to do a
// `('localStorage' in window) && ` test first because otherwise Firefox will
// throw bugzil.la/365772 if cookies are disabled
// Also in iOS5 Private Browsing mode, attepting to use localStorage.setItem
// will throw the exception:
// QUOTA_EXCEEDED_ERRROR DOM Exception 22.
// Peculiarly, getItem and removeItem calls do not throw.
// Because we are forced to try/catch this, we'll go aggressive.
// Just FWIW: IE8 Compat mode supports these features completely:
// www.quirksmode.org/dom/html5.html
// But IE8 doesn't support either with local files
tests['localstorage'] = function() {
try {
localStorage.setItem(mod, mod);
localStorage.removeItem(mod);
return true;
} catch(e) {
return false;
}
};
tests['sessionstorage'] = function() {
try {
sessionStorage.setItem(mod, mod);
sessionStorage.removeItem(mod);
return true;
} catch(e) {
return false;
}
};
tests['webworkers'] = function() {
return !!window.Worker;
};
tests['applicationcache'] = function() {
return !!window.applicationCache;
};
// Thanks to Erik Dahlstrom
tests['svg'] = function() {
return !!document.createElementNS && !!document.createElementNS(ns.svg, 'svg').createSVGRect;
};
// specifically for SVG inline in HTML, not within XHTML
// test page: paulirish.com/demo/inline-svg
tests['inlinesvg'] = function() {
var div = document.createElement('div');
div.innerHTML = '<svg/>';
return (div.firstChild && div.firstChild.namespaceURI) == ns.svg;
};
// SVG SMIL animation
tests['smil'] = function() {
return !!document.createElementNS && /SVGAnimate/.test(toString.call(document.createElementNS(ns.svg, 'animate')));
};
// This test is only for clip paths in SVG proper, not clip paths on HTML content
// demo: srufaculty.sru.edu/david.dailey/svg/newstuff/clipPath4.svg
// However read the comments to dig into applying SVG clippaths to HTML content here:
// github.com/Modernizr/Modernizr/issues/213#issuecomment-1149491
tests['svgclippaths'] = function() {
return !!document.createElementNS && /SVGClipPath/.test(toString.call(document.createElementNS(ns.svg, 'clipPath')));
};
// input features and input types go directly onto the ret object, bypassing the tests loop.
// Hold this guy to execute in a moment.
function webforms() {
// Run through HTML5's new input attributes to see if the UA understands any.
// We're using f which is the <input> element created early on
// Mike Taylr has created a comprehensive resource for testing these attributes
// when applied to all input types:
// miketaylr.com/code/input-type-attr.html
// spec: www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html#input-type-attr-summary
// Only input placeholder is tested while textarea's placeholder is not.
// Currently Safari 4 and Opera 11 have support only for the input placeholder
// Both tests are available in feature-detects/forms-placeholder.js
Modernizr['input'] = (function( props ) {
for ( var i = 0, len = props.length; i < len; i++ ) {
attrs[ props[i] ] = !!(props[i] in inputElem);
}
if (attrs.list){
// safari false positive's on datalist: webk.it/74252
// see also github.com/Modernizr/Modernizr/issues/146
attrs.list = !!(document.createElement('datalist') && window.HTMLDataListElement);
}
return attrs;
})('autocomplete autofocus list placeholder max min multiple pattern required step'.split(' '));
// Run through HTML5's new input types to see if the UA understands any.
// This is put behind the tests runloop because it doesn't return a
// true/false like all the other tests; instead, it returns an object
// containing each input type with its corresponding true/false value
// Big thanks to @miketaylr for the html5 forms expertise. miketaylr.com/
Modernizr['inputtypes'] = (function(props) {
for ( var i = 0, bool, inputElemType, defaultView, len = props.length; i < len; i++ ) {
inputElem.setAttribute('type', inputElemType = props[i]);
bool = inputElem.type !== 'text';
// We first check to see if the type we give it sticks..
// If the type does, we feed it a textual value, which shouldn't be valid.
// If the value doesn't stick, we know there's input sanitization which infers a custom UI
if ( bool ) {
inputElem.value = smile;
inputElem.style.cssText = 'position:absolute;visibility:hidden;';
if ( /^range$/.test(inputElemType) && inputElem.style.WebkitAppearance !== undefined ) {
docElement.appendChild(inputElem);
defaultView = document.defaultView;
// Safari 2-4 allows the smiley as a value, despite making a slider
bool = defaultView.getComputedStyle &&
defaultView.getComputedStyle(inputElem, null).WebkitAppearance !== 'textfield' &&
// Mobile android web browser has false positive, so must
// check the height to see if the widget is actually there.
(inputElem.offsetHeight !== 0);
docElement.removeChild(inputElem);
} else if ( /^(search|tel)$/.test(inputElemType) ){
// Spec doesnt define any special parsing or detectable UI
// behaviors so we pass these through as true
// Interestingly, opera fails the earlier test, so it doesn't
// even make it here.
} else if ( /^(url|email)$/.test(inputElemType) ) {
// Real url and email support comes with prebaked validation.
bool = inputElem.checkValidity && inputElem.checkValidity() === false;
} else if ( /^color$/.test(inputElemType) ) {
// chuck into DOM and force reflow for Opera bug in 11.00
// github.com/Modernizr/Modernizr/issues#issue/159
docElement.appendChild(inputElem);
docElement.offsetWidth;
bool = inputElem.value != smile;
docElement.removeChild(inputElem);
} else {
// If the upgraded input compontent rejects the :) text, we got a winner
bool = inputElem.value != smile;
}
}
inputs[ props[i] ] = !!bool;
}
return inputs;
})('search tel url email datetime date month week time datetime-local number range color'.split(' '));
}
// End of test definitions
// -----------------------
// Run through all tests and detect their support in the current UA.
// todo: hypothetically we could be doing an array of tests and use a basic loop here.
for ( var feature in tests ) {
if ( hasOwnProperty(tests, feature) ) {
// run the test, throw the return value into the Modernizr,
// then based on that boolean, define an appropriate className
// and push it into an array of classes we'll join later.
featureName = feature.toLowerCase();
Modernizr[featureName] = tests[feature]();
classes.push((Modernizr[featureName] ? '' : 'no-') + featureName);
}
}
// input tests need to run.
Modernizr.input || webforms();
/**
* addTest allows the user to define their own feature tests
* the result will be added onto the Modernizr object,
* as well as an appropriate className set on the html element
*
* @param feature - String naming the feature
* @param test - Function returning true if feature is supported, false if not
*/
Modernizr.addTest = function ( feature, test ) {
if ( typeof feature == 'object' ) {
for ( var key in feature ) {
if ( hasOwnProperty( feature, key ) ) {
Modernizr.addTest( key, feature[ key ] );
}
}
} else {
feature = feature.toLowerCase();
if ( Modernizr[feature] !== undefined ) {
// we're going to quit if you're trying to overwrite an existing test
// if we were to allow it, we'd do this:
// var re = new RegExp("\\b(no-)?" + feature + "\\b");
// docElement.className = docElement.className.replace( re, '' );
// but, no rly, stuff 'em.
return Modernizr;
}
test = typeof test == 'function' ? test() : test;
docElement.className += ' ' + (test ? '' : 'no-') + feature;
Modernizr[feature] = test;
}
return Modernizr; // allow chaining.
};
// Reset modElem.cssText to nothing to reduce memory footprint.
setCss('');
modElem = inputElem = null;
//>>BEGIN IEPP
// Enable HTML 5 elements for styling (and printing) in IE.
if ( window.attachEvent && (function(){ var elem = document.createElement('div');
elem.innerHTML = '<elem></elem>';
return elem.childNodes.length !== 1; })() ) {
// iepp v2.2 MIT/GPL2 @jon_neal & afarkas : github.com/aFarkas/iepp/
(function(win, doc) {
win.iepp = win.iepp || {};
var iepp = win.iepp,
elems = iepp.html5elements || 'abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|subline|summary|time|video',
elemsArr = elems.split('|'),
elemsArrLen = elemsArr.length,
elemRegExp = new RegExp('(^|\\s)('+elems+')', 'gi'),
tagRegExp = new RegExp('<(\/*)('+elems+')', 'gi'),
filterReg = /^\s*[\{\}]\s*$/,
ruleRegExp = new RegExp('(^|[^\\n]*?\\s)('+elems+')([^\\n]*)({[\\n\\w\\W]*?})', 'gi'),
nonPrintMedias = /@media +(?![Print|All])[^{]+\{([^{}]+\{[^{}]+\})+[^}]+\}/g,
docFrag = doc.createDocumentFragment(),
html = doc.documentElement,
head = doc.getElementsByTagName('script')[0].parentNode,
bodyElem = doc.createElement('body'),
styleElem = doc.createElement('style'),
printMedias = /print|all/,
body;
function shim(doc) {
var a = -1;
while (++a < elemsArrLen) {
// Use createElement so IE allows HTML5-named elements in a document
doc.createElement(elemsArr[a]);
}
}
iepp.getCSS = function(styleSheetList, mediaType) {
try {
if(styleSheetList+'' === undefined){
return '';
}
} catch(er){
return '';
}
var a = -1,
len = styleSheetList.length,
styleSheet,
cssText,
cssTextArr = [];
while (++a < len) {
styleSheet = styleSheetList[a];
//currently no test for disabled/alternate stylesheets
if(styleSheet.disabled){
continue;
}
mediaType = styleSheet.media || mediaType;
// Get css from all non-screen stylesheets and their imports
if (printMedias.test(mediaType)){
cssText = styleSheet.cssText;
if(mediaType != 'print'){
cssText = cssText.replace(nonPrintMedias, '');
}
cssTextArr.push(iepp.getCSS(styleSheet.imports, mediaType), cssText);
}
//reset mediaType to all with every new *not imported* stylesheet
mediaType = 'all';
}
return cssTextArr.join('');
};
iepp.parseCSS = function(cssText) {
var cssTextArr = [],
rule;
while ((rule = ruleRegExp.exec(cssText)) != null){
// Replace all html5 element references with iepp substitute classnames
cssTextArr.push(( (filterReg.exec(rule[1]) ? '\n' : rule[1]) + rule[2] + rule[3]).replace(elemRegExp, '$1.iepp-$2') + rule[4]);
}
return cssTextArr.join('\n');
};
iepp.writeHTML = function() {
var a = -1;