1 /**
  2  * @fileOverview ua
  3  * @author lifesinger@gmail.com
  4  */
  5 KISSY.add('ua/base', function (S, undefined) {
  6 
  7     var win = S.Env.host,
  8         navigator = win.navigator,
  9         ua = navigator.userAgent,
 10         EMPTY = '',
 11         MOBILE = 'mobile',
 12         core = EMPTY,
 13         shell = EMPTY, m,
 14         IE_DETECT_RANGE = [6, 9],
 15         v,
 16         end,
 17         VERSION_PLACEHOLDER = '{{version}}',
 18         IE_DETECT_TPL = '<!--[if IE ' + VERSION_PLACEHOLDER + ']><' + 's></s><![endif]-->',
 19         div = win.document.createElement('div'),
 20         s,
 21         o = {
 22             // browser core type
 23             webkit:undefined,
 24             trident:undefined,
 25             gecko:undefined,
 26             presto:undefined,
 27 
 28             // browser type
 29             chrome:undefined,
 30             safari:undefined,
 31             firefox:undefined,
 32             ie:undefined,
 33             opera:undefined,
 34 
 35             mobile:undefined,
 36             core:undefined,
 37             shell:undefined
 38         },
 39         numberify = function (s) {
 40             var c = 0;
 41             // convert '1.2.3.4' to 1.234
 42             return parseFloat(s.replace(/\./g, function () {
 43                 return (c++ === 0) ? '.' : '';
 44             }));
 45         };
 46 
 47     // try to use IE-Conditional-Comment detect IE more accurately
 48     // IE10 doesn't support this method, @ref: http://blogs.msdn.com/b/ie/archive/2011/07/06/html5-parsing-in-ie10.aspx
 49     div.innerHTML = IE_DETECT_TPL.replace(VERSION_PLACEHOLDER, '');
 50     s = div.getElementsByTagName('s');
 51 
 52     if (s.length > 0) {
 53 
 54         shell = 'ie';
 55         o[core = 'trident'] = 0.1; // Trident detected, look for revision
 56 
 57         // Get the Trident's accurate version
 58         if ((m = ua.match(/Trident\/([\d.]*)/)) && m[1]) {
 59             o[core] = numberify(m[1]);
 60         }
 61 
 62         // Detect the accurate version
 63         // 注意:
 64         //  o.shell = ie, 表示外壳是 ie
 65         //  但 o.ie = 7, 并不代表外壳是 ie7, 还有可能是 ie8 的兼容模式
 66         //  对于 ie8 的兼容模式,还要通过 documentMode 去判断。但此处不能让 o.ie = 8, 否则
 67         //  很多脚本判断会失误。因为 ie8 的兼容模式表现行为和 ie7 相同,而不是和 ie8 相同
 68         for (v = IE_DETECT_RANGE[0], end = IE_DETECT_RANGE[1]; v <= end; v++) {
 69             div.innerHTML = IE_DETECT_TPL.replace(VERSION_PLACEHOLDER, v);
 70             if (s.length > 0) {
 71                 o[shell] = v;
 72                 break;
 73             }
 74         }
 75 
 76     } else {
 77 
 78         // WebKit
 79         if ((m = ua.match(/AppleWebKit\/([\d.]*)/)) && m[1]) {
 80             o[core = 'webkit'] = numberify(m[1]);
 81 
 82             // Chrome
 83             if ((m = ua.match(/Chrome\/([\d.]*)/)) && m[1]) {
 84                 o[shell = 'chrome'] = numberify(m[1]);
 85             }
 86             // Safari
 87             else if ((m = ua.match(/\/([\d.]*) Safari/)) && m[1]) {
 88                 o[shell = 'safari'] = numberify(m[1]);
 89             }
 90 
 91             // Apple Mobile
 92             if (/ Mobile\//.test(ua)) {
 93                 o[MOBILE] = 'apple'; // iPad, iPhone or iPod Touch
 94             }
 95             // Other WebKit Mobile Browsers
 96             else if ((m = ua.match(/NokiaN[^\/]*|Android \d\.\d|webOS\/\d\.\d/))) {
 97                 o[MOBILE] = m[0].toLowerCase(); // Nokia N-series, Android, webOS, ex: NokiaN95
 98             }
 99         }
100         // NOT WebKit
101         else {
102             // Presto
103             // ref: http://www.useragentstring.com/pages/useragentstring.php
104             if ((m = ua.match(/Presto\/([\d.]*)/)) && m[1]) {
105                 o[core = 'presto'] = numberify(m[1]);
106 
107                 // Opera
108                 if ((m = ua.match(/Opera\/([\d.]*)/)) && m[1]) {
109                     o[shell = 'opera'] = numberify(m[1]); // Opera detected, look for revision
110 
111                     if ((m = ua.match(/Opera\/.* Version\/([\d.]*)/)) && m[1]) {
112                         o[shell] = numberify(m[1]);
113                     }
114 
115                     // Opera Mini
116                     if ((m = ua.match(/Opera Mini[^;]*/)) && m) {
117                         o[MOBILE] = m[0].toLowerCase(); // ex: Opera Mini/2.0.4509/1316
118                     }
119                     // Opera Mobile
120                     // ex: Opera/9.80 (Windows NT 6.1; Opera Mobi/49; U; en) Presto/2.4.18 Version/10.00
121                     // issue: 由于 Opera Mobile 有 Version/ 字段,可能会与 Opera 混淆,同时对于 Opera Mobile 的版本号也比较混乱
122                     else if ((m = ua.match(/Opera Mobi[^;]*/)) && m) {
123                         o[MOBILE] = m[0];
124                     }
125                 }
126 
127                 // NOT WebKit or Presto
128             } else {
129                 // MSIE
130                 // 由于最开始已经使用了 IE 条件注释判断,因此落到这里的唯一可能性只有 IE10+
131                 if ((m = ua.match(/MSIE\s([^;]*)/)) && m[1]) {
132                     o[core = 'trident'] = 0.1; // Trident detected, look for revision
133                     o[shell = 'ie'] = numberify(m[1]);
134 
135                     // Get the Trident's accurate version
136                     if ((m = ua.match(/Trident\/([\d.]*)/)) && m[1]) {
137                         o[core] = numberify(m[1]);
138                     }
139 
140                     // NOT WebKit, Presto or IE
141                 } else {
142                     // Gecko
143                     if ((m = ua.match(/Gecko/))) {
144                         o[core = 'gecko'] = 0.1; // Gecko detected, look for revision
145                         if ((m = ua.match(/rv:([\d.]*)/)) && m[1]) {
146                             o[core] = numberify(m[1]);
147                         }
148 
149                         // Firefox
150                         if ((m = ua.match(/Firefox\/([\d.]*)/)) && m[1]) {
151                             o[shell = 'firefox'] = numberify(m[1]);
152                         }
153                     }
154                 }
155             }
156         }
157     }
158 
159     o.core = core;
160     o.shell = shell;
161     o._numberify = numberify;
162     return o;
163 });
164 
165 /**
166  * NOTES:
167  *
168  * 2011.11.08
169  *  - ie < 10 使用条件注释判断内核,更精确 by gonghaocn@gmail.com
170  *
171  * 2010.03
172  *  - jQuery, YUI 等类库都推荐用特性探测替代浏览器嗅探。特性探测的好处是能自动适应未来设备和未知设备,比如
173  *    if(document.addEventListener) 假设 IE9 支持标准事件,则代码不用修改,就自适应了“未来浏览器”。
174  *    对于未知浏览器也是如此。但是,这并不意味着浏览器嗅探就得彻底抛弃。当代码很明确就是针对已知特定浏览器的,
175  *    同时并非是某个特性探测可以解决时,用浏览器嗅探反而能带来代码的简洁,同时也也不会有什么后患。总之,一切
176  *    皆权衡。
177  *  - UA.ie && UA.ie < 8 并不意味着浏览器就不是 IE8, 有可能是 IE8 的兼容模式。进一步的判断需要使用 documentMode.
178  *
179  * TODO:
180  *  - test mobile
181  *  - 3Q 大战后,360 去掉了 UA 信息中的 360 信息,需采用 res 方法去判断
182  *
183  */
184