尽管按钮上直接绑了一个内联的事件,但事件模型并不买账,仍然得按标准的流程走一遍。capture,target,bubble,模型是那样固执。
  不过,把那行注释的代码恢复,结果只剩 capture 了。这个简单的道理大家都明白,也没什么好解释的。
  但仔细揣摩下,这不是『主动防御』的概念吗?捕获程序运行在内联事件触发之前,并且完全有能力拦截之后的调用。
  上面的 Demo 只是不假思索拦截了所有的事件。如果我们再加一些策略判断,或许更明朗了:

 

<button onclick="console.log('xss')">CLICK ME</button>
<script>
document.addEventListener('click', function(e) {
console.log('bubble');
});
document.addEventListener('click', function(e) {
var element = e.target;
var code = element.getAttribute('onclick');
if (/xss/.test(code)) {
e.stopImmediatePropagation();
console.log('拦截可疑事件:', code);
}
}, true);
</script>
Run

  我们先在捕获阶段扫描内联事件字符,若是出现了『xss』这个关键字,后续的事件被拦截了;换成其他字符,仍然继续执行。同理,我们还可以判断字符长度是否过多,以及更详细的黑白名单正则。
  怎么样,一个主动防御的原型诞生了吧。
  不过,上面的片段还有个小问题,是把事件的冒泡过程也给屏蔽了,而我们仅仅想拦截内联事件而已。解决办法也很简单,把 e.stopImmediatePropagation() 换成 element.onclick = null 可以了。
  当然,目前这只能防护 onclick,而现实中有太多的内联事件。鼠标、键盘、触屏、网络状态等等,不同浏览器支持的事件也不一样,甚至还有私有事件,难道都要事先逐一列出并且都捕获吗?是的,可以都捕获,但不必事先都列出来。
  因为我们监听的是 document 对象,浏览器所有内联事件都对应着 document.onxxx 的属性,因此只需运行时遍历一下 document 对象,即可获得所有的事件名。
<img src="*" onerror="console.log('xss')" />

 

<script>
function hookEvent(onevent) {
document.addEventListener(onevent.substr(2), function(e) {
var element = e.target;
if (element.nodeType != Node.ELEMENT_NODE) {
return;
}
var code = element.getAttribute(onevent);
if (code && /xss/.test(code)) {
element[onevent] = null;
console.log('拦截可疑事件:', code);
}
}, true);
}
console.time('耗时');
for (var k in document) {
if (/^on/.test(k)) {
//console.log('监控:', k);
hookEvent(k);
}
}
console.timeEnd('耗时');
</script>
Run

  现在,无论页面中哪个元素触发哪个内联事件,都能预先被我们捕获,并根据策略可进可退了。