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