由UserAgent问题翻开的一则旧闻

背景

原文:由UserAgent问题翻开的一则旧闻

下午,同事很紧急的找到我说,某个场景下,进入到项目某个页面白屏。经过定位发现是之前因为jsencrypt.js来实现rsa, 整个包很大,不支持按需加载又需要在首屏加载。所以考虑到根据开源方案自己实现一个轻量化的rsa加密。所以就用到了jsbn的方案,抽取了必要的逻辑。其中有一段生成随机字符串生成的逻辑rng.js

// Initialize the pool with junk if needed.
if(rng_pool == null) {
  rng_pool = new Array();
  rng_pptr = 0;
  var t;
  if(window.crypto && window.crypto.getRandomValues) {
    // Use webcrypto if available
    var ua = new Uint8Array(32);
    window.crypto.getRandomValues(ua);
    for(t = 0; t < 32; ++t)
      rng_pool[rng_pptr++] = ua[t];
  }
  if(navigator.appName == "Netscape" && navigator.appVersion < "5" && window.crypto) {
    // Extract entropy (256 bits) from NS4 RNG if available
    var z = window.crypto.random(32);
    for(t = 0; t < z.length; ++t)
      rng_pool[rng_pptr++] = z.charCodeAt(t) & 255;
  }  
  while(rng_pptr < rng_psize) {  // extract some randomness from Math.random()
    t = Math.floor(65536 * Math.random());
    rng_pool[rng_pptr++] = t >>> 8;
    rng_pool[rng_pptr++] = t & 255;
  }
  rng_pptr = 0;
  rng_seed_time();
  //rng_seed_int(window.screenX);
  //rng_seed_int(window.screenY);
}

其中有一段对于ua的判断,使用了navigator 相关的属性,兼容低版本浏览器。 戏剧性的是,我们网页运行在安卓的webview内,而webview的navigator属性是自己设置的,就导致了进入了低版本逻辑判断中,然后高版本内核的浏览器是没有这个方法导致了报错, 通过修改源码兼容处理以及安卓同事正确的设置webview navigator解决掉了这个问题。

虽然一直都有使用到浏览器navigator属性来判断各种浏览器版本,但是一直有个疑问没有深究过。

为什么浏览器的userAgent 信息中有那么多信息, 以Microsoft Edge 为例, 里面有几个关键词Mozilla, AppleWebkit, KHtml, like Gecko, Chrome, Edg, 众所周知, Microsoft Edge 使用chrome内核带chrome 标识可以理解。chrome 为什么会带 safari等其他标识呢。这牵涉到一桩旧闻,浏览器大战, 资料来源维基 和 Mozilla官网。

Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.102 Safari/537.36 Edg/104.0.1293.63

浏览器大战

来到 1995 年,Netscape Navigator 已不是上网冲浪的唯一选择。计算机软件巨头 Microsoft 获得了旧版 Mosaic 的代码授权,从而建立了自己的“Web 窗口”—— Internet Explorer。该浏览器的推出引发了战争。Netscape 与 Microsoft 竭力为各自的产品推出新版本,试图以更快、更好的产品来超越对方。

Netscape 设计并推出了 JavaScript,从而让网站拥有了前所未有的强大计算能力。(他们还创造了声名狼藉的 <blink> 标签。)而 Microsoft 一方也反对后来成为网页设计标准的层叠样式表(CSS)。

1997 年,Microsoft 发行了 Internet Explorer 4.0,事情逐渐失控。Microsoft 团队制作了一个大大的字母“e”并放在 Netscape 总部的草坪上。Netscape 团队迅速敲碎了这个巨大的字母,并将吉祥物 Mozilla 恐龙放在上面。

接下来,Microsoft 开始将 Internet Explorer 与 Windows 操作系统捆绑发行。四年内,IE 获得了 75% 的市场份额,到 1999 年甚至达到了 99% 。此举最终使 Microsoft 面临反垄断诉讼。意识到垄断浏览器市场并不符合用户与开放网络的最佳利益后,Netscape 决定开放源代码,转型为非营利的 Mozilla,并于 2002 年发布 Firefox。到了 2010 年,Mozilla Firefox 及其他浏览器将 Internet Explorer 的市场份额降低到 50%。

浏览器市场在上世纪 90 年代末和本世纪初涌现出许多竞争者,包括 Opera、Safari、Google Chrome。2015 年,Microsoft 发布 Windows 10,用 Microsoft Edge 取代了 Internet Explorer。

混乱的UserAgent

UserAgent 就反应了他们之间不断竞争的过程。

NCSA_Mosaic

最早的UserAgent 是NCSA Mosaic, 他的UserAgent是

NCSA_Mosaic/2.0(Windows 3.1)

Mozilla & Gecko

接着就是重要的Mozilla, 含义是Mosaic Killer的缩写, 然后因为Mosaic的反对,Mizilla 改名Netscape(网景)

Mozilla/1.0(Win3.1)

因为Netscape 先支持 Freames 特性, 所以当时的开发者会监测浏览器的UA,判断是Mozilla, 会返回Frames, 其他UA则不返回。

彼时的Netscape公司更多次尝试创做一种能够让用户通过浏览器操作的网络应用系统,微软担心Netscape浏览器影响windows系统的销量,购买了NCSA Mosaic的授权, 在此基础上开发了Internet Explorer。虽然IE 也支持Frames, 但是开发者只会给UA 包含Mozilla 的浏览器返回Frames。微软灵机一动,

Mozilla/1.22 (compatible; MSIE 2.0; Windows 95)

诶嘿, 我的UA也是Mozilla, 你给我返回Frames吧。 果然生效了。由此UA便有由IE开启了叠甲之路。

接下来就是IE 和 Netscape的第一次浏览大战,以IE的胜利告终,Netscape 浏览器黯然退场。但是Netscape的研发并未停滞,继续研发了Gecko内核, (2003年,Netscape倒闭,Gecko由Netscape成立的Mozilla 社区继续开发), 其UA设置为

Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.1) Gecko/20020826

随着Gecko内核的流行, 越来越多的浏览器使用Gecko内核, 进入万物皆Gecko 时代。

// 亲生的Firfox Mozilla/5.0 (Windows; U; Windows NT 5.1; sv-SE; rv:1.7.5) Gecko/20041108 Firefox/1.0 Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.7.2) Gecko/20040825 Camino/0.8.1 Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.8.1.8) Gecko/20071008 SeaMonkey/1.0

这期间Oprea 说你们这搞的太复杂了,你看我的。然后就给用户开放了一个可以切换UA的功能, 用户想要啥自己切换吧,这样依赖UA就更加的不可靠了。

Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; en) Opera 9.51, Mozilla/5.0 (Windows NT 6.0; U; en; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.51, Opera/9.51 (Windows NT 5.1; U; en) ,

KHTML

除此之外,Linux用户开发了Konqueror浏览器,它的引擎是KHTML,他们认为和Gecko一样好,但他不是Gecko,web开发者不会返回更好的排版页面,于是Konquerer开始假装像 Gecko 一样获得好页面。

Mozilla/5.0 (compatible; Konqueror/3.2; FreeBSD) (KHTML, like Gecko)

WebKit

Apple公司 Fork了 KHTML进行二次开发,添加了许多功能, 并将其称为 WebKit,并且构建了Safari 浏览器,其UA设置为

Mozilla/5.0(Macintosh;U;PPC Mac OS X;de -de) AppleWebKit/85.7 (KHTML, like Gecko) Safari/85.5

接下来就是Goolge 公司基于webkit 构建了 chrome 浏览器。根据目前的浏览器市场份额,这也是目前为止最常见的UA了。

Mozilla/5.0(Windows;U;Windows NT 5.1; en-US) AppleWebKit/525.13 (KHTML, like Gecko) Chrome/0.2.149.27 Safari/525.13

image-1662007747816

由此就进入了webkit内核统治的时代。目前市场上webkit内核占到了95%以上。

当年让所有浏览器都要伪装的Gecko, 目前只剩下Firfox在苦苦支撑,但是份额已久在逐年减少,甚有传闻Google 会给Mozilla 基金会捐赠,以避免chrome陷入垄断的泥潭^1^,除此之外Opera老版会有Presto内核不过基本可以忽略不计,连其他都算不上。

剩下的就基本都是webkit 内核了,此情此景,不仅令人唏嘘。统一的内核就有了统一的规范, 对于Web开发者来说也许是幸运的事吧。

UserAgent

这也是为什么Mozilla一直不推荐我们使用UA来做兼容处理的原因,过于混乱。可以被修改,可以模拟。

Edge 说我是Chrome, Chrome 说我是Safari, Safari 说我是KHTML, KHTML 说我就像 Gecko 一样,Gecko 说我不装了,我就是Mozilla。

至此,我们了解了UA 为什么那么长,其实就是一层层的马甲, 告诉开发者我兼容某些功能。

还有一类属于是搜索引擎爬虫UserAgent不做过多阐述,搜索引擎外,用户自己写的爬虫就更不讲武德了,UA对于他们来说就是欺骗开发者,返回他们需要的数据。

回到文章的起点,我们的项目其实是运行在webview中的,上面介绍的基本都是标准的内核,但是像webview这些就更加五花八门了,因为开发者可以自定义设置。标准一点的会沿用默认的UA数据,添加自定义的内容,比如微信,微博。还有一些不标准的就有可能不设置UA或者设置错误的UA。

比如这次引起白屏问题的webview的UA, XXX为自定义字段。安卓错误的设置底层虚拟机Agent 给到了webview的UA。

Dalvik/2.1.0 (Linux; U; Android 12; SM-G9980 Build/SP1A.210812.016) XXXXXXXXXXXXXXXXX

怎么避免这种问题MDN UserAgent 文档给了相关建议的,。

值得重申的是:使用用户代理嗅探很少会成为一个好主意。你几乎总能发现一个更好的、更广泛兼容的方式来解决你的问题。 如果你想要尝试避免使用用户代理检测,在某些情形下有些可供选择的方法。

功能检测 使用功能检测,你不需要弄清楚是哪种浏览器正在渲染你的页面,你只需要检测你需要的某个特定功能是否可用即可。如果该功能不可用,使用一个应变策略。然而,在某些罕见的情形下,你确实需要检测浏览器种类,由于其他浏览器也会在将来以不同方式实现这个功能,绝不要使用功能检测来判断。由此造成的错误将非常难发现和修复。

渐进增强 这种设计技术涉及了按照“层次”开发网页,使用自底向上的方法,从一个简单的层次开始,在一系列连续的层次中通过使用更多的功能来逐步提升站点的能力。

优雅降级 这是一种自顶向下的方式,在搭建可能最好的的站点时使用所有你想要的功能,然后稍微改进使其能在更老版本的浏览器上工作。与渐进增强的方法相比,这种方法可能更困难、效率更低,不过在一些情景下也许是有效的。

参考资料

Timeline_of_web_browsers.svg

Mozilla 资金来源

User-Agent detection, history and checklist

History of the browser user-agent string