• 如何让网页中的copy事件失效?
  • 发布于 2个月前
  • 176 热度
    0 评论

当我想在网页复制点内容,ctrl + c 时被要求告知需要加入vip,需要登陆,完了还得在我复制得内容后面随意拉屎。对于这样的流氓行为,我不想惯着它了,搞它。搞它前,先要知道,网页是怎么知道我复制内容了的?


js是运行在浏览器的唯一脚本语言,为了实现网页与用户的各种交互行为,实现了n多事件,最为常见的比如click点击事件。这些众多事件中,就有一个copy事件,用来监听用户的复制行为,比如执行ctrl + c 或是 右键复制。


现在知道了copy,那要阻止流氓行为,只要让网页中的copy事件失效就可以了。问题是,怎么才能让它失效?到这里,先搞清楚网页是如何绑定一个事件的?

一般来说绑定事件有 **三种常见形式**:
1.使用on+事件名称直接绑定
2.使用addEventListener函数绑定
3.使用行内属性进行绑定

第一种,**使用on+事件名称直接绑定**
该如何处理,先来一段代码:
<body>  
     <div class="copy-box">  
          <p>text content</p>  
     </div>  
</body>
上述代码中,得到一个这样的结构:body -> div.copy-box -> p。

我们分别在div.copy-box 、document、window上绑定一个click事件,如下:
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[2], writable[3], enumerable[4]、configurable[5]})

要实现禁止对象的属性值被修改,只需关注writable[6] 即可,得到以下代码:
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函数绑定**。
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是函数,那是不是可以提前篡改这个函数?
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,会发现绑定的事件也不会在触发了,再次达到了禁止的目的。

最后再来搞定第三种事件绑定形式:**使用行内属性进行绑定。**
// html  
html  ->  onclick="clickfn('html')"  
body -> onclick="clickfn('body')"
div -> onclick="clickfn('dom')"
上述代码中,我们在div、body、html处分别绑定了click事件。要达到事件失效,只需要干掉这个onclick属性即可。
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了。

贴上一份完整代码:
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)
  })
})
这么让它在所有网页运行,**篡改猴** 你值得拥有。
// ==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';
   // 上述完整代码放这
})();

用户评论