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