隐私保护看起来近些年变得热门,其实在 Web 标准的历史上还是为了隐私做了不少考量,甚至付出代价。这里举两个例子:
关于 :visited
伪类
这个伪类能够被大家知道可能很大程度上归功于浏览器的默认样式:IE 和它的继承者们都会默认将蓝色的超链接在访问后标记成紫色;有的网站则可能会自定义访问过的链接颜色,这似乎就是 :visited
的唯一使用场景了。
后来我们有了 querySelector
和 querySelectorAll
。可以试试看,在任何一个页面点击一个链接,再在控制台调用 document.querySelector(':visited')
,结果返回的依然是 null
,这是为什么呢?
假设我们创建了一个博客网站,并且引用了【分享到 QQ 空间】的 JS SDK。如果 JS SDK 内部包含了类似于查询 :visited
元素的代码,显然,用户的访问记录将全部被 JS SDK 获取到;甚至 SDK 也可以创建一个透明的包含常见网站链接的容器,它甚至能够知道我们访问过哪些其他网站!为了阻止类似的事情发生,浏览器“欺骗”了 querySelector
,让它无法得知 :visited
的存在。
同样的道理,能够通过 :visited
覆盖的样式也是有限的,例如 color
等寥寥数个而已。考虑一下假设我们能够用 :visited
声明 line-height
,那么第三方脚本将能够通过链接列表 -> 创建隐藏元素 -> 计算高度的方式间接知道这些链接是否被访问,从而达到和之前一样的获取隐私的效果。
关于浏览器缓存
如果你做过性能优化,你是否想过,在浏览器缓存一个资源之后,这个资源在其他网站访问时也会被缓存吗?
答案曾经是肯定的。这似乎很符合直觉,因为全局缓存看起来没有什么问题,甚至如果我们大量使用了公共 CDN,这些来自 CDN 的模块就能够提前在其他的网站被加载了。
但是!缓存本身也可能被作为用户的标识使用,你能想到是什么吗?
……
没错,就是 ETag
。
假设浏览器拥有一个全局缓存的策略(事实上曾经也是这样的),仍然以 QQ 空间 SDK 为例,SDK 可以请求一个资源,比方说是一个 GIF 吧。GIF 的服务端可以让这个资源总是使用协商缓存(no-cache
),并且为其下发一个类似于 Web ID 的 ETag。于是,如果另一个网站也引用了 QQ 空间 SDK,那么 QQ 空间就能通过这些网站请求时的 If-None-Match
得知这些请求都来自于同一个用户了。
你可能会觉得这个过程好熟悉——没错,这其实就是“第三方 Cookie”。在各大浏览器绞尽脑汁禁止第三方 Cookie 的今天,缓存也最终被限制为仅在同源站点内生效,通过牺牲可能微不足道的一点加载性能来换取更好的隐私保护。