Upgrade to ExtJS 4.0.1 - Released 05/18/2011
[extjs.git] / src / core / src / Support.js
1 /**
2  * @class Ext.is
3  * 
4  * Determines information about the current platform the application is running on.
5  * 
6  * @singleton
7  */
8 Ext.is = {
9     init : function(navigator) {
10         var platforms = this.platforms,
11             ln = platforms.length,
12             i, platform;
13
14         navigator = navigator || window.navigator;
15
16         for (i = 0; i < ln; i++) {
17             platform = platforms[i];
18             this[platform.identity] = platform.regex.test(navigator[platform.property]);
19         }
20
21         /**
22          * @property Desktop True if the browser is running on a desktop machine
23          * @type {Boolean}
24          */
25         this.Desktop = this.Mac || this.Windows || (this.Linux && !this.Android);
26         /**
27          * @property Tablet True if the browser is running on a tablet (iPad)
28          */
29         this.Tablet = this.iPad;
30         /**
31          * @property Phone True if the browser is running on a phone.
32          * @type {Boolean}
33          */
34         this.Phone = !this.Desktop && !this.Tablet;
35         /**
36          * @property iOS True if the browser is running on iOS
37          * @type {Boolean}
38          */
39         this.iOS = this.iPhone || this.iPad || this.iPod;
40         
41         /**
42          * @property Standalone Detects when application has been saved to homescreen.
43          * @type {Boolean}
44          */
45         this.Standalone = !!window.navigator.standalone;
46     },
47     
48     /**
49      * @property iPhone True when the browser is running on a iPhone
50      * @type {Boolean}
51      */
52     platforms: [{
53         property: 'platform',
54         regex: /iPhone/i,
55         identity: 'iPhone'
56     },
57     
58     /**
59      * @property iPod True when the browser is running on a iPod
60      * @type {Boolean}
61      */
62     {
63         property: 'platform',
64         regex: /iPod/i,
65         identity: 'iPod'
66     },
67     
68     /**
69      * @property iPad True when the browser is running on a iPad
70      * @type {Boolean}
71      */
72     {
73         property: 'userAgent',
74         regex: /iPad/i,
75         identity: 'iPad'
76     },
77     
78     /**
79      * @property Blackberry True when the browser is running on a Blackberry
80      * @type {Boolean}
81      */
82     {
83         property: 'userAgent',
84         regex: /Blackberry/i,
85         identity: 'Blackberry'
86     },
87     
88     /**
89      * @property Android True when the browser is running on an Android device
90      * @type {Boolean}
91      */
92     {
93         property: 'userAgent',
94         regex: /Android/i,
95         identity: 'Android'
96     },
97     
98     /**
99      * @property Mac True when the browser is running on a Mac
100      * @type {Boolean}
101      */
102     {
103         property: 'platform',
104         regex: /Mac/i,
105         identity: 'Mac'
106     },
107     
108     /**
109      * @property Windows True when the browser is running on Windows
110      * @type {Boolean}
111      */
112     {
113         property: 'platform',
114         regex: /Win/i,
115         identity: 'Windows'
116     },
117     
118     /**
119      * @property Linux True when the browser is running on Linux
120      * @type {Boolean}
121      */
122     {
123         property: 'platform',
124         regex: /Linux/i,
125         identity: 'Linux'
126     }]
127 };
128
129 Ext.is.init();
130
131 /**
132  * @class Ext.supports
133  *
134  * Determines information about features are supported in the current environment
135  * 
136  * @singleton
137  */
138 Ext.supports = {
139     init : function() {
140         var doc = document,
141             div = doc.createElement('div'),
142             tests = this.tests,
143             ln = tests.length,
144             i, test;
145
146         div.innerHTML = [
147             '<div style="height:30px;width:50px;">',
148                 '<div style="height:20px;width:20px;"></div>',
149             '</div>',
150             '<div style="width: 200px; height: 200px; position: relative; padding: 5px;">',
151                 '<div style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"></div>',
152             '</div>',
153             '<div style="float:left; background-color:transparent;"></div>'
154         ].join('');
155
156         doc.body.appendChild(div);
157
158         for (i = 0; i < ln; i++) {
159             test = tests[i];
160             this[test.identity] = test.fn.call(this, doc, div);
161         }
162
163         doc.body.removeChild(div);
164     },
165
166     /**
167      * @property CSS3BoxShadow True if document environment supports the CSS3 box-shadow style.
168      * @type {Boolean}
169      */
170     CSS3BoxShadow: Ext.isDefined(document.documentElement.style.boxShadow),
171
172     /**
173      * @property ClassList True if document environment supports the HTML5 classList API.
174      * @type {Boolean}
175      */
176     ClassList: !!document.documentElement.classList,
177
178     /**
179      * @property OrientationChange True if the device supports orientation change
180      * @type {Boolean}
181      */
182     OrientationChange: ((typeof window.orientation != 'undefined') && ('onorientationchange' in window)),
183     
184     /**
185      * @property DeviceMotion True if the device supports device motion (acceleration and rotation rate)
186      * @type {Boolean}
187      */
188     DeviceMotion: ('ondevicemotion' in window),
189     
190     /**
191      * @property Touch True if the device supports touch
192      * @type {Boolean}
193      */
194     // is.Desktop is needed due to the bug in Chrome 5.0.375, Safari 3.1.2
195     // and Safari 4.0 (they all have 'ontouchstart' in the window object).
196     Touch: ('ontouchstart' in window) && (!Ext.is.Desktop),
197
198     tests: [
199         /**
200          * @property Transitions True if the device supports CSS3 Transitions
201          * @type {Boolean}
202          */
203         {
204             identity: 'Transitions',
205             fn: function(doc, div) {
206                 var prefix = [
207                         'webkit',
208                         'Moz',
209                         'o',
210                         'ms',
211                         'khtml'
212                     ],
213                     TE = 'TransitionEnd',
214                     transitionEndName = [
215                         prefix[0] + TE,
216                         'transitionend', //Moz bucks the prefixing convention
217                         prefix[2] + TE,
218                         prefix[3] + TE,
219                         prefix[4] + TE
220                     ],
221                     ln = prefix.length,
222                     i = 0,
223                     out = false;
224                 div = Ext.get(div);
225                 for (; i < ln; i++) {
226                     if (div.getStyle(prefix[i] + "TransitionProperty")) {
227                         Ext.supports.CSS3Prefix = prefix[i];
228                         Ext.supports.CSS3TransitionEnd = transitionEndName[i];
229                         out = true;
230                         break;
231                     }
232                 }
233                 return out;
234             }
235         },
236         
237         /**
238          * @property RightMargin True if the device supports right margin.
239          * See https://bugs.webkit.org/show_bug.cgi?id=13343 for why this is needed.
240          * @type {Boolean}
241          */
242         {
243             identity: 'RightMargin',
244             fn: function(doc, div) {
245                 var view = doc.defaultView;
246                 return !(view && view.getComputedStyle(div.firstChild.firstChild, null).marginRight != '0px');
247             }
248         },
249
250         /**
251          * @property DisplayChangeInputSelectionBug True if INPUT elements lose their
252          * selection when their display style is changed. Essentially, if a text input
253          * has focus and its display style is changed, the I-beam disappears.
254          * 
255          * This bug is encountered due to the work around in place for the {@link RightMargin}
256          * bug. This has been observed in Safari 4.0.4 and older, and appears to be fixed
257          * in Safari 5. It's not clear if Safari 4.1 has the bug, but it has the same WebKit
258          * version number as Safari 5 (according to http://unixpapa.com/js/gecko.html).
259          */
260         {
261             identity: 'DisplayChangeInputSelectionBug',
262             fn: function() {
263                 var webKitVersion = Ext.webKitVersion;
264                 // WebKit but older than Safari 5 or Chrome 6:
265                 return 0 < webKitVersion && webKitVersion < 533;
266             }
267         },
268
269         /**
270          * @property DisplayChangeTextAreaSelectionBug True if TEXTAREA elements lose their
271          * selection when their display style is changed. Essentially, if a text area has
272          * focus and its display style is changed, the I-beam disappears.
273          *
274          * This bug is encountered due to the work around in place for the {@link RightMargin}
275          * bug. This has been observed in Chrome 10 and Safari 5 and older, and appears to
276          * be fixed in Chrome 11.
277          */
278         {
279             identity: 'DisplayChangeTextAreaSelectionBug',
280             fn: function() {
281                 var webKitVersion = Ext.webKitVersion;
282
283                 /*
284                 Has bug w/textarea:
285
286                 (Chrome) Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; en-US)
287                             AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.127
288                             Safari/534.16
289                 (Safari) Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; en-us)
290                             AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5
291                             Safari/533.21.1
292
293                 No bug:
294
295                 (Chrome) Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_7)
296                             AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.57
297                             Safari/534.24
298                 */
299                 return 0 < webKitVersion && webKitVersion < 534.24;
300             }
301         },
302
303         /**
304          * @property TransparentColor True if the device supports transparent color
305          * @type {Boolean}
306          */
307         {
308             identity: 'TransparentColor',
309             fn: function(doc, div, view) {
310                 view = doc.defaultView;
311                 return !(view && view.getComputedStyle(div.lastChild, null).backgroundColor != 'transparent');
312             }
313         },
314
315         /**
316          * @property ComputedStyle True if the browser supports document.defaultView.getComputedStyle()
317          * @type {Boolean}
318          */
319         {
320             identity: 'ComputedStyle',
321             fn: function(doc, div, view) {
322                 view = doc.defaultView;
323                 return view && view.getComputedStyle;
324             }
325         },
326         
327         /**
328          * @property SVG True if the device supports SVG
329          * @type {Boolean}
330          */
331         {
332             identity: 'Svg',
333             fn: function(doc) {
334                 return !!doc.createElementNS && !!doc.createElementNS( "http:/" + "/www.w3.org/2000/svg", "svg").createSVGRect;
335             }
336         },
337     
338         /**
339          * @property Canvas True if the device supports Canvas
340          * @type {Boolean}
341          */
342         {
343             identity: 'Canvas',
344             fn: function(doc) {
345                 return !!doc.createElement('canvas').getContext;
346             }
347         },
348         
349         /**
350          * @property VML True if the device supports VML
351          * @type {Boolean}
352          */
353         {
354             identity: 'Vml',
355             fn: function(doc) {
356                 var d = doc.createElement("div");
357                 d.innerHTML = "<!--[if vml]><br><br><![endif]-->";
358                 return (d.childNodes.length == 2);
359             }
360         },
361         
362         /**
363          * @property Float True if the device supports CSS float
364          * @type {Boolean}
365          */
366         {
367             identity: 'Float',
368             fn: function(doc, div) {
369                 return !!div.lastChild.style.cssFloat;
370             }
371         },
372         
373         /**
374          * @property AudioTag True if the device supports the HTML5 audio tag
375          * @type {Boolean}
376          */
377         {
378             identity: 'AudioTag',
379             fn: function(doc) {
380                 return !!doc.createElement('audio').canPlayType;
381             }
382         },
383         
384         /**
385          * @property History True if the device supports HTML5 history
386          * @type {Boolean}
387          */
388         {
389             identity: 'History',
390             fn: function() {
391                 return !!(window.history && history.pushState);
392             }
393         },
394         
395         /**
396          * @property CSS3DTransform True if the device supports CSS3DTransform
397          * @type {Boolean}
398          */
399         {
400             identity: 'CSS3DTransform',
401             fn: function() {
402                 return (typeof WebKitCSSMatrix != 'undefined' && new WebKitCSSMatrix().hasOwnProperty('m41'));
403             }
404         },
405
406                 /**
407          * @property CSS3LinearGradient True if the device supports CSS3 linear gradients
408          * @type {Boolean}
409          */
410         {
411             identity: 'CSS3LinearGradient',
412             fn: function(doc, div) {
413                 var property = 'background-image:',
414                     webkit   = '-webkit-gradient(linear, left top, right bottom, from(black), to(white))',
415                     w3c      = 'linear-gradient(left top, black, white)',
416                     moz      = '-moz-' + w3c,
417                     options  = [property + webkit, property + w3c, property + moz];
418                 
419                 div.style.cssText = options.join(';');
420                 
421                 return ("" + div.style.backgroundImage).indexOf('gradient') !== -1;
422             }
423         },
424         
425         /**
426          * @property CSS3BorderRadius True if the device supports CSS3 border radius
427          * @type {Boolean}
428          */
429         {
430             identity: 'CSS3BorderRadius',
431             fn: function(doc, div) {
432                 var domPrefixes = ['borderRadius', 'BorderRadius', 'MozBorderRadius', 'WebkitBorderRadius', 'OBorderRadius', 'KhtmlBorderRadius'],
433                     pass = false,
434                     i;
435                 for (i = 0; i < domPrefixes.length; i++) {
436                     if (document.body.style[domPrefixes[i]] !== undefined) {
437                         return true;
438                     }
439                 }
440                 return pass;
441             }
442         },
443         
444         /**
445          * @property GeoLocation True if the device supports GeoLocation
446          * @type {Boolean}
447          */
448         {
449             identity: 'GeoLocation',
450             fn: function() {
451                 return (typeof navigator != 'undefined' && typeof navigator.geolocation != 'undefined') || (typeof google != 'undefined' && typeof google.gears != 'undefined');
452             }
453         },
454         /**
455          * @property MouseEnterLeave True if the browser supports mouseenter and mouseleave events
456          * @type {Boolean}
457          */
458         {
459             identity: 'MouseEnterLeave',
460             fn: function(doc, div){
461                 return ('onmouseenter' in div && 'onmouseleave' in div);
462             }
463         },
464         /**
465          * @property MouseWheel True if the browser supports the mousewheel event
466          * @type {Boolean}
467          */
468         {
469             identity: 'MouseWheel',
470             fn: function(doc, div) {
471                 return ('onmousewheel' in div);
472             }
473         },
474         /**
475          * @property Opacity True if the browser supports normal css opacity
476          * @type {Boolean}
477          */
478         {
479             identity: 'Opacity',
480             fn: function(doc, div){
481                 // Not a strict equal comparison in case opacity can be converted to a number.
482                 if (Ext.isIE6 || Ext.isIE7 || Ext.isIE8) {
483                     return false;
484                 }
485                 div.firstChild.style.cssText = 'opacity:0.73';
486                 return div.firstChild.style.opacity == '0.73';
487             }
488         },
489         /**
490          * @property Placeholder True if the browser supports the HTML5 placeholder attribute on inputs
491          * @type {Boolean}
492          */
493         {
494             identity: 'Placeholder',
495             fn: function(doc) {
496                 return 'placeholder' in doc.createElement('input');
497             }
498         },
499         
500         /**
501          * @property Direct2DBug True if when asking for an element's dimension via offsetWidth or offsetHeight, 
502          * getBoundingClientRect, etc. the browser returns the subpixel width rounded to the nearest pixel.
503          * @type {Boolean}
504          */
505         {
506             identity: 'Direct2DBug',
507             fn: function() {
508                 return Ext.isString(document.body.style.msTransformOrigin);
509             }
510         },
511         /**
512          * @property BoundingClientRect True if the browser supports the getBoundingClientRect method on elements
513          * @type {Boolean}
514          */
515         {
516             identity: 'BoundingClientRect',
517             fn: function(doc, div) {
518                 return Ext.isFunction(div.getBoundingClientRect);
519             }
520         },
521         {
522             identity: 'IncludePaddingInWidthCalculation',
523             fn: function(doc, div){
524                 var el = Ext.get(div.childNodes[1].firstChild);
525                 return el.getWidth() == 210;
526             }
527         },
528         {
529             identity: 'IncludePaddingInHeightCalculation',
530             fn: function(doc, div){
531                 var el = Ext.get(div.childNodes[1].firstChild);
532                 return el.getHeight() == 210;
533             }
534         },
535         
536         /**
537          * @property ArraySort True if the Array sort native method isn't bugged.
538          * @type {Boolean}
539          */
540         {
541             identity: 'ArraySort',
542             fn: function() {
543                 var a = [1,2,3,4,5].sort(function(){ return 0; });
544                 return a[0] === 1 && a[1] === 2 && a[2] === 3 && a[3] === 4 && a[4] === 5;
545             }
546         },
547         /**
548          * @property Range True if browser support document.createRange native method.
549          * @type {Boolean}
550          */
551         {
552             identity: 'Range',
553             fn: function() {
554                 return !!document.createRange;
555             }
556         },
557         /**
558          * @property CreateContextualFragment True if browser support CreateContextualFragment range native methods.
559          * @type {Boolean}
560          */
561         {
562             identity: 'CreateContextualFragment',
563             fn: function() {
564                 var range = Ext.supports.Range ? document.createRange() : false;
565                 
566                 return range && !!range.createContextualFragment;
567             }
568         },
569
570         /**
571          * @property WindowOnError True if browser supports window.onerror.
572          * @type {Boolean}
573          */
574         {
575             identity: 'WindowOnError',
576             fn: function () {
577                 // sadly, we cannot feature detect this...
578                 return Ext.isIE || Ext.isGecko || Ext.webKitVersion >= 534.16; // Chrome 10+
579             }
580         }
581     ]
582 };