当我想在网页复制点内容,ctrl + c 时被要求告知需要加入vip,需要登陆,完了还得在我复制得内容后面随意拉屎。
对于这样的流氓行为,我不想惯着它了,搞它。
搞它前,先要知道,网页是怎么知道我复制内容了的?
js是运行在浏览器的唯一脚本语言,为了实现网页与用户的各种交互行为,实现了n多事件,最为常见的比如click点击事件。这些众多事件中,就有一个copy事件,用来监听用户的复制行为,比如执行ctrl + c
或是 右键复制
。
现在知道了copy,那要阻止流氓行为,只要让网页中的copy事件失效就可以了。问题是,怎么才能让它失效?
到这里,先搞清楚网页是如何绑定一个事件的?
一般来说绑定事件有 三种常见形式
:
-
使用on+事件名称直接绑定
-
使用addEventListener函数绑定
-
使用行内属性进行绑定
第一种,使用on+事件名称直接绑定
该如何处理,先来一段代码:
js
<body>
<div class="copy-box">
<p>text content</p>
</div>
</body>
上述代码中,得到一个这样的结构:body -> div.copy-box -> p。
我们分别在div.copy-box 、document、window上绑定一个click事件,如下:
js
const ev = 'onclick'
document.querySelector('.copy-box')[ev] = () => console.log('dom event: ' + ev)
document[ev] = () => console.log('document event: ' + ev)
window[ev] = () => console.log('window event: ' + ev)
根据js的事件冒泡机制,事件会从实际触发的节点依次向上冒泡。在上述代码里,window对象包含了整个网页的所有内容。document对象就是文档对象,包含了网页了所有节点信息,document也是window下的一个对象。根据包含关系得到以下结构:window -> document -> html ->body -> div.copy-box -> p。所以在上述代码执行时,会依次输出dom event: onclick、document event: onclick、window event: onclick。
敏锐一点,可以发现,事件的绑定都是在各自的最后出现[ev],ev是一个变量,值是:onclick。在不使用变量的情况下, 就是onclick。这就是js 对象数据类型的经典使用方式。
[ev] === .onclick
. 这样完全可以把上述代码中的document.querySelector('.copy-box')、document、window看成对象结构,onclick就是这个对象中的某一个键,后面运行的函数就是值。
既然是对象,那么是不是只要让这个对象的键onclick不被修改就可以了。是的, 想要锁定对象的值不能被修改,就得寄出 Object.defineProperty。
defineProperty的基本语法:Object.defineProperty(对象, 键, {value, writable, enumerable、configurable})
要实现禁止对象的属性值被修改,只需关注writable 即可,得到以下代码:
js
const o = { value: null, writable: false }
Object.defineProperty(HTMLElement.prototype, ev, o)
Object.defineProperty(Document.prototype, ev, o)
Object.defineProperty(window, ev, o)
让这段代码放在事件绑定之前运行,在页面再次点击div.copy-box,会发现绑定的事件不会在触发,达到了禁止的目的。
再来看这二种事件绑定的形式:使用addEventListener函数绑定
。
js
const ev = 'click'
document.querySelector('.copy-box').addEventListener(ev, () => console.log('dom event: ' + ev))
document.addEventListener(ev, () => console.log('document event: ' + ev))
window.addEventListener(ev, () => console.log('window event: ' + ev))
我们发现,这时的事件绑定都使用了addEventListener函数,事件通过函数参数进行绑定,没有了之前的类似对象的键值关系,之前的禁止代码对于addEventListener失去了作用。但是,addEventListener是函数,那是不是可以提前篡改这个函数?
js
HTMLElement.prototype._addEventListener = Element.prototype.addEventListener;
Document.prototype._addEventListener = Document.prototype.addEventListener;
Window.prototype._addEventListener = Window.prototype.addEventListener;
HTMLElement.prototype.addEventListener = _xaddEventListener;
Document.prototype.addEventListener = _xaddEventListener;
Window.prototype.addEventListener = _xaddEventListener;
function _xaddEventListener(a, b, c) {
if (a != ev) this._addEventListener(a, b, c);
};
这段代码中,我们在各自的原型链找到addEventListener,同时写一个自定义的_addEventListener函数,先将原本的addEventListener赋值给它。接着开始篡改原始addEventListener
函数,篡改的函数里对传入的事件进行判断,识别到click,直接丢弃不在处理。这是再次点击div.copy-box,会发现绑定的事件也不会在触发了,再次达到了禁止的目的。
最后再来搞定第三种事件绑定形式:使用行内属性进行绑定。
js
// html
html -> onclick="clickfn('html')"
body -> onclick="clickfn('body')"
div -> onclick="clickfn('dom')"
上述代码中,我们在div、body、html处分别绑定了click事件。要达到事件失效,只需要干掉这个onclick属性即可。
js
window.addEventListener('DOMContentLoaded', function() {
const clicks = [...document.querySelectorAll('[' + onev + ']')]
clicks.forEach(item => {
item.removeAttribute(onev)
item.removeAttribute(onev)
})
})
DOMContentLoaded
MDN 的解释:当初始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,而无需等待样式表、图像和子框架的完成加载。
上述代码里,我们将逻辑处理放到了事件DOMContentLoaded里运行,为了干掉所有的onclick属性,使用document.querySelectorAll来搜索。循环移除。
至此,事件的禁止就ok了。
贴上一份完整代码:
js
const ev = 'copy'
const onev = 'on' + ev
const o = { value: null, writable: false }
Object.defineProperty(HTMLElement.prototype, onev, o)
Object.defineProperty(Document.prototype, onev, o)
Object.defineProperty(window, onev, o)
HTMLElement.prototype._addEventListener = Element.prototype.addEventListener;
Document.prototype._addEventListener = Document.prototype.addEventListener;
Window.prototype._addEventListener = Window.prototype.addEventListener;
HTMLElement.prototype.addEventListener = _xaddEventListener;
Document.prototype.addEventListener = _xaddEventListener;
Window.prototype.addEventListener = _xaddEventListener;
function _xaddEventListener(a, b, c) {
if (a != ev) this._addEventListener(a, b, c);
};
window.addEventListener('DOMContentLoaded', function() {
const clicks = [...document.querySelectorAll('[' + onev + ']')]
clicks.forEach(item => {
item.removeAttribute(onev)
item.removeAttribute(onev)
})
})
这么让它在所有网页运行,篡改猴
你值得拥有。
js
// ==UserScript==
// @name 移除所有网页绑定的copy函数,实现自由复制
// @version 1.0
// @description try to take over the world!
// @author -
// @match *://*/*
// @icon https://g.csdnimg.cn/static/logo/favicon32.ico
// @grant none
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
// 上述完整代码放这
})();