Upgrade to ExtJS 4.0.0 - Released 04/26/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, view) {
245                 view = doc.defaultView;
246                 return !(view && view.getComputedStyle(div.firstChild.firstChild, null).marginRight != '0px');
247             }
248         },
249         
250         /**
251          * @property TransparentColor True if the device supports transparent color
252          * @type {Boolean}
253          */
254         {
255             identity: 'TransparentColor',
256             fn: function(doc, div, view) {
257                 view = doc.defaultView;
258                 return !(view && view.getComputedStyle(div.lastChild, null).backgroundColor != 'transparent');
259             }
260         },
261
262         /**
263          * @property ComputedStyle True if the browser supports document.defaultView.getComputedStyle()
264          * @type {Boolean}
265          */
266         {
267             identity: 'ComputedStyle',
268             fn: function(doc, div, view) {
269                 view = doc.defaultView;
270                 return view && view.getComputedStyle;
271             }
272         },
273         
274         /**
275          * @property SVG True if the device supports SVG
276          * @type {Boolean}
277          */
278         {
279             identity: 'Svg',
280             fn: function(doc) {
281                 return !!doc.createElementNS && !!doc.createElementNS( "http:/" + "/www.w3.org/2000/svg", "svg").createSVGRect;
282             }
283         },
284     
285         /**
286          * @property Canvas True if the device supports Canvas
287          * @type {Boolean}
288          */
289         {
290             identity: 'Canvas',
291             fn: function(doc) {
292                 return !!doc.createElement('canvas').getContext;
293             }
294         },
295         
296         /**
297          * @property VML True if the device supports VML
298          * @type {Boolean}
299          */
300         {
301             identity: 'Vml',
302             fn: function(doc) {
303                 var d = doc.createElement("div");
304                 d.innerHTML = "<!--[if vml]><br><br><![endif]-->";
305                 return (d.childNodes.length == 2);
306             }
307         },
308         
309         /**
310          * @property Float True if the device supports CSS float
311          * @type {Boolean}
312          */
313         {
314             identity: 'Float',
315             fn: function(doc, div) {
316                 return !!div.lastChild.style.cssFloat;
317             }
318         },
319         
320         /**
321          * @property AudioTag True if the device supports the HTML5 audio tag
322          * @type {Boolean}
323          */
324         {
325             identity: 'AudioTag',
326             fn: function(doc) {
327                 return !!doc.createElement('audio').canPlayType;
328             }
329         },
330         
331         /**
332          * @property History True if the device supports HTML5 history
333          * @type {Boolean}
334          */
335         {
336             identity: 'History',
337             fn: function() {
338                 return !!(window.history && history.pushState);
339             }
340         },
341         
342         /**
343          * @property CSS3DTransform True if the device supports CSS3DTransform
344          * @type {Boolean}
345          */
346         {
347             identity: 'CSS3DTransform',
348             fn: function() {
349                 return (typeof WebKitCSSMatrix != 'undefined' && new WebKitCSSMatrix().hasOwnProperty('m41'));
350             }
351         },
352
353                 /**
354          * @property CSS3LinearGradient True if the device supports CSS3 linear gradients
355          * @type {Boolean}
356          */
357         {
358             identity: 'CSS3LinearGradient',
359             fn: function(doc, div) {
360                 var property = 'background-image:',
361                     webkit   = '-webkit-gradient(linear, left top, right bottom, from(black), to(white))',
362                     w3c      = 'linear-gradient(left top, black, white)',
363                     moz      = '-moz-' + w3c,
364                     options  = [property + webkit, property + w3c, property + moz];
365                 
366                 div.style.cssText = options.join(';');
367                 
368                 return ("" + div.style.backgroundImage).indexOf('gradient') !== -1;
369             }
370         },
371         
372         /**
373          * @property CSS3BorderRadius True if the device supports CSS3 border radius
374          * @type {Boolean}
375          */
376         {
377             identity: 'CSS3BorderRadius',
378             fn: function(doc, div) {
379                 var domPrefixes = ['borderRadius', 'BorderRadius', 'MozBorderRadius', 'WebkitBorderRadius', 'OBorderRadius', 'KhtmlBorderRadius'],
380                     pass = false,
381                     i;
382                 for (i = 0; i < domPrefixes.length; i++) {
383                     if (document.body.style[domPrefixes[i]] !== undefined) {
384                         return true;
385                     }
386                 }
387                 return pass;
388             }
389         },
390         
391         /**
392          * @property GeoLocation True if the device supports GeoLocation
393          * @type {Boolean}
394          */
395         {
396             identity: 'GeoLocation',
397             fn: function() {
398                 return (typeof navigator != 'undefined' && typeof navigator.geolocation != 'undefined') || (typeof google != 'undefined' && typeof google.gears != 'undefined');
399             }
400         },
401         /**
402          * @property MouseEnterLeave True if the browser supports mouseenter and mouseleave events
403          * @type {Boolean}
404          */
405         {
406             identity: 'MouseEnterLeave',
407             fn: function(doc, div){
408                 return ('onmouseenter' in div && 'onmouseleave' in div);
409             }
410         },
411         /**
412          * @property MouseWheel True if the browser supports the mousewheel event
413          * @type {Boolean}
414          */
415         {
416             identity: 'MouseWheel',
417             fn: function(doc, div) {
418                 return ('onmousewheel' in div);
419             }
420         },
421         /**
422          * @property Opacity True if the browser supports normal css opacity
423          * @type {Boolean}
424          */
425         {
426             identity: 'Opacity',
427             fn: function(doc, div){
428                 // Not a strict equal comparison in case opacity can be converted to a number.
429                 if (Ext.isIE6 || Ext.isIE7 || Ext.isIE8) {
430                     return false;
431                 }
432                 div.firstChild.style.cssText = 'opacity:0.73';
433                 return div.firstChild.style.opacity == '0.73';
434             }
435         },
436         /**
437          * @property Placeholder True if the browser supports the HTML5 placeholder attribute on inputs
438          * @type {Boolean}
439          */
440         {
441             identity: 'Placeholder',
442             fn: function(doc) {
443                 return 'placeholder' in doc.createElement('input');
444             }
445         },
446         
447         /**
448          * @property Direct2DBug True if when asking for an element's dimension via offsetWidth or offsetHeight, 
449          * getBoundingClientRect, etc. the browser returns the subpixel width rounded to the nearest pixel.
450          * @type {Boolean}
451          */
452         {
453             identity: 'Direct2DBug',
454             fn: function() {
455                 return Ext.isString(document.body.style.msTransformOrigin);
456             }
457         },
458         /**
459          * @property BoundingClientRect True if the browser supports the getBoundingClientRect method on elements
460          * @type {Boolean}
461          */
462         {
463             identity: 'BoundingClientRect',
464             fn: function(doc, div) {
465                 return Ext.isFunction(div.getBoundingClientRect);
466             }
467         },
468         {
469             identity: 'IncludePaddingInWidthCalculation',
470             fn: function(doc, div){
471                 var el = Ext.get(div.childNodes[1].firstChild);
472                 return el.getWidth() == 210;
473             }
474         },
475         {
476             identity: 'IncludePaddingInHeightCalculation',
477             fn: function(doc, div){
478                 var el = Ext.get(div.childNodes[1].firstChild);
479                 return el.getHeight() == 210;
480             }
481         },
482         
483         /**
484          * @property ArraySort True if the Array sort native method isn't bugged.
485          * @type {Boolean}
486          */
487         {
488             identity: 'ArraySort',
489             fn: function() {
490                 var a = [1,2,3,4,5].sort(function(){ return 0; });
491                 return a[0] === 1 && a[1] === 2 && a[2] === 3 && a[3] === 4 && a[4] === 5;
492             }
493         },
494         /**
495          * @property Range True if browser support document.createRange native method.
496          * @type {Boolean}
497          */
498         {
499             identity: 'Range',
500             fn: function() {
501                 return !!document.createRange;
502             }
503         },
504         /**
505          * @property CreateContextualFragment True if browser support CreateContextualFragment range native methods.
506          * @type {Boolean}
507          */
508         {
509             identity: 'CreateContextualFragment',
510             fn: function() {
511                 var range = Ext.supports.Range ? document.createRange() : false;
512                 
513                 return range && !!range.createContextualFragment;
514             }
515         }
516         
517     ]
518 };