Our original blog address: https://blog.rxliuli.com/p/4b2822b2/
We have released an oil monkey script, which can be installed directly Remove page restrictions For a better experience.
scene
One thing that often happens when browsing the web, when we want to copy, suddenly we find that copying seems useless? ( Articles prohibited by Zhihu )Or there's a little bit more in the end of the copy.( Brief book ), or not at all( 360doc ) You may find that it can't be pasted all the time.( Alipay login).
problem
If you want to defeat the enemy first, you must first confuse the enemy. To remove the restrictions of copy and paste, it is necessary to know how they are implemented. In any case, all that can run on the browser is JavaScript, and they are all implemented using JavaScript. The implementation mode is basically to listen to the corresponding events (for example, onkeydown listens to Ctrl-C), and then do some special operations.
For example, only one sentence of code is needed to block the copy function.
document.oncopy = event => false
Yes, as long as false is returned, the copy will fail. There is a more annoying way to add inline events directly to the body element
<body oncopy="javascript: return false" />
Solve
As you can see, JavaScript is generally used to return false in the corresponding event to prevent the corresponding event. So, since the events have been stopped, does it mean that we are helpless? The solutions we can think of have three directions
-
Use JavaScript to monitor events and implement copy / cut / paste function
- Advantage: after the implementation, no matter any website can be used, and it will not affect events other than listening, and it will not delete the same type of events listening, and it can release the restriction of browser itself (password box forbids copying)
- Disadvantages: some functions are difficult to realize by themselves, such as selecting text
-
Re implement the addEventListener and delete the customized events of the website
- Advantages: the event has a wide range of effectiveness and high universality. It can not only copy / cut / paste, but also release other types of events.
- Disadvantages: the addEventListener event needs to be replaced early enough to implement. The default operation on the browser will not take effect (password box forbids copying), and some websites cannot be cracked.
-
Replace elements and remove event attributes on DOM
- Advantages: it can ensure that the restrictions of website js are lifted, high universality and wide range of events.
-
Disadvantage: it may affect other types of events. When copying nodes, events added with addEventListener will not be copied.
Note: this method is not demonstrated, and the defect is too large.
In short, if you really want to lift the restrictions, I'm afraid you need to use both ways.
Use JavaScript to monitor events and implement copy / cut / paste function
Implement forced replication
thinking
- Bubble monitoring copy event
- Get the currently selected content
- Set up cut content
- Prevent default event handling
// Compulsory replication document.addEventListener( 'copy', event => { event.clipboardData.setData( 'text/plain', document.getSelection().toString(), ) // Block default event handling event.preventDefault() }, true, )
Implement forced cutting
thinking
- Bubble listening to cut events
- Get the currently selected content
- Set up cut content
- If it is editable, delete the selected part.
- Prevent default event handling
You can see that the only thing that needs to be added is that you need to deal with the editable content. However, the amount of code exploded in a flash.
/** * String safe conversion to lowercase * @param {String} str Character string * @returns {String} All lowercase string after conversion */ function toLowerCase(str) { if (!str || typeof str !== 'string') { return str } return str.toLowerCase() } /** * Determine whether the specified element is an editable element * Note: editable elements are not necessarily editable, such as read-only input elements * @param {Element} el Elements to be judged * @returns {Boolean} Editable element or not */ function isEditable(el) { var inputEls = ['input', 'date', 'datetime', 'select', 'textarea'] return ( el && (el.isContentEditable || inputEls.includes(toLowerCase(el.tagName))) ) } /** * Get the cursor position in the input box * @param {Element} el Input box elements to get * @returns {Number} Subscript of cursor position */ function getCusorPostion(el) { return el.selectionStart } /** * Set the position of the selected text / cursor in the input box * @param {Element} el Input box elements to set * @param {Number} start Subscript of cursor position * @param {Number} {end} End position, default to end of input box */ function setCusorPostion(el, start, end = start) { el.focus() el.setSelectionRange(start, end) } /** * Delete text within the specified range * @param {Element} el Input box elements to set * @param {Number} {start} Start position, default to the currently selected start position * @param {Number} {end} End position, default to the currently selected end position */ function removeText(el, start = el.selectionStart, end = el.selectionEnd) { // You must [remember] the position of the current cursor before deleting var index = getCusorPostion(el) var value = el.value el.value = value.substr(0, start) + value.substr(end, value.length) setCusorPostion(el, index) } // Forced shear document.addEventListener( 'cut', event => { event.clipboardData.setData( 'text/plain', document.getSelection().toString(), ) // If it's an editable element, delete it. if (isEditable(event.target)) { removeText(event.target) } event.preventDefault() }, true, )
Implement forced paste
- Bubble listens for focus/blur to get the last editable element to get focus
- Bubble monitoring paste event
- Get cut content
- Get the last editable element to get focus
- Delete the currently selected text
- Insert text at current cursor
- Prevent default event handling
/** * Get to the last element in focus */ var getLastFocus = (lastFocusEl => { document.addEventListener( 'focus', event => { lastFocusEl = event.target }, true, ) document.addEventListener( 'blur', event => { lastFocusEl = null }, true, ) return () => lastFocusEl })(null) /** * String safe conversion to lowercase * @param {String} str Character string * @returns {String} All lowercase string after conversion */ function toLowerCase(str) { if (!str || typeof str !== 'string') { return str } return str.toLowerCase() } /** * Determine whether the specified element is an editable element * Note: editable elements are not necessarily editable, such as read-only input elements * @param {Element} el Elements to be judged * @returns {Boolean} Editable element or not */ function isEditable(el) { var inputEls = ['input', 'date', 'datetime', 'select', 'textarea'] return ( el && (el.isContentEditable || inputEls.includes(toLowerCase(el.tagName))) ) } /** * Get the cursor position in the input box * @param {Element} el Input box elements to be retrieved * @returns {Number} Subscript of cursor position */ function getCusorPostion(el) { return el.selectionStart } /** * Set the position of the selected text / cursor in the input box * @param {Element} el Input box elements to set * @param {Number} start Subscript of cursor position * @param {Number} {end} End position, default to end of input box */ function setCusorPostion(el, start, end = start) { el.focus() el.setSelectionRange(start, end) } /** * Insert text after specified location * @param {Element} el Input box elements to set * @param {String} value Value to insert * @param {Number} {start} Start position, defaults to the current cursor position */ function insertText(el, text, start = getCusorPostion(el)) { var value = el.value el.value = value.substr(0, start) + text + value.substr(start) setCusorPostion(el, start + text.length) } /** * Delete text within the specified range * @param {Element} el Input box elements to set * @param {Number} {start} Start position, default to the currently selected start position * @param {Number} {end} End position, default to the currently selected end position */ function removeText(el, start = el.selectionStart, end = el.selectionEnd) { // You must [remember] the position of the current cursor before deleting var index = getCusorPostion(el) var value = el.value el.value = value.substr(0, start) + value.substr(end, value.length) setCusorPostion(el, index) } // Mandatory paste document.addEventListener( 'paste', event => { // Get current clipboard content var clipboardData = event.clipboardData var items = clipboardData.items var item = items[0] if (item.kind !== 'string') { return } var text = clipboardData.getData(item.type) // Get current focus element // Can't get focus when pasting? var focusEl = getLastFocus() // Isn't input an editable element? if (isEditable(focusEl)) { removeText(focusEl) insertText(focusEl, text) event.preventDefault() } }, true, )
summary
Full text of script
;(function() { 'use strict' /** * Two ideas: * 1. Self realization * 2. Replacement elements */ /** * Get to the last element in focus */ var getLastFocus = (lastFocusEl => { document.addEventListener( 'focus', event => { lastFocusEl = event.target }, true, ) document.addEventListener( 'blur', event => { lastFocusEl = null }, true, ) return () => lastFocusEl })(null) /** * String safe conversion to lowercase * @param {String} str Character string * @returns {String} All lowercase string after conversion */ function toLowerCase(str) { if (!str || typeof str !== 'string') { return str } return str.toLowerCase() } /** * String safe conversion to uppercase * @param {String} str Character string * @returns {String} All uppercase string after conversion */ function toUpperCase(str) { if (!str || typeof str !== 'string') { return str } return str.toUpperCase() } /** * Determine whether the specified element is an editable element * Note: editable elements are not necessarily editable, such as read-only input elements * @param {Element} el Elements to be judged * @returns {Boolean} Editable element or not */ function isEditable(el) { var inputEls = ['input', 'date', 'datetime', 'select', 'textarea'] return ( el && (el.isContentEditable || inputEls.includes(toLowerCase(el.tagName))) ) } /** * Get the cursor position in the input box * @param {Element} el Input box elements to get * @returns {Number} Subscript of cursor position */ function getCusorPostion(el) { return el.selectionStart } /** * Set the position of the selected text / cursor in the input box * @param {Element} el Input box elements to set * @param {Number} start Subscription of cursor position * @param {Number} {end} End position, default to end of input box */ function setCusorPostion(el, start, end = start) { el.focus() el.setSelectionRange(start, end) } /** * Insert text after the specified location * @param {Element} el Input box elements to set * @param {String} value Value to insert * @param {Number} {start} Start position, defaults to the current cursor position */ function insertText(el, text, start = getCusorPostion(el)) { var value = el.value el.value = value.substr(0, start) + text + value.substr(start) setCusorPostion(el, start + text.length) } /** * Delete text within the specified range * @param {Element} el Input box elements to set * @param {Number} {start} Start position, default to the currently selected start position * @param {Number} {end} End position, default to the currently selected end position */ function removeText(el, start = el.selectionStart, end = el.selectionEnd) { // You must [remember] the position of the current cursor before deleting var index = getCusorPostion(el) var value = el.value el.value = value.substr(0, start) + value.substr(end, value.length) setCusorPostion(el, index) } // Compulsory replication document.addEventListener( 'copy', event => { event.clipboardData.setData( 'text/plain', document.getSelection().toString(), ) event.preventDefault() }, true, ) // Forced shear document.addEventListener( 'cut', event => { event.clipboardData.setData( 'text/plain', document.getSelection().toString(), ) // If it's an editable element, delete it. if (isEditable(event.target)) { removeText(event.target) } event.preventDefault() }, true, ) // Mandatory paste document.addEventListener( 'paste', event => { // Get current clipboard content var clipboardData = event.clipboardData var items = clipboardData.items var item = items[0] if (item.kind !== 'string') { return } var text = clipboardData.getData(item.type) // Get current focus element // Can't get focus when pasting? var focusEl = getLastFocus() // Isn't input an editable element? if (isEditable(focusEl)) { removeText(focusEl) insertText(focusEl, text) event.preventDefault() } }, true, ) function selection() { var dom document.onmousedown = event => { dom = event.target // console.log('click:, dom) debugger console.log('Where the cursor is: ', getCusorPostion(dom)) } document.onmousemove = event => { console.log('move: ', dom) } document.onmouseup = event => { console.log('Release: ', dom) } } })()
Re implement the addEventListener and delete the customized events of the website
The realization is inspired by https://greasyfork.org/en/scr... , which almost perfectly realizes the function of removing restrictions
The principle is very simple. Modify the prototype and re implement the addEventListener function of EventTarget and docuement.
// ==UserScript== // @name remove web page restrictions // @namespace http://github.com/rxliuli // @version 1.0 // @description crack the website with copy / cut / paste / select / right-click menu forbidden // @author rxliuli // @include https://www.jianshu.com/* // @grant GM.getValue // @grant GM.setValue // The @ run at here is very important. It is set to load the script at the beginning of the document. // @run-at document-start // ==/UserScript== ;(() => { /** * Listen to all addeventlistener and removeeventlistener events */ var documentAddEventListener = document.addEventListener var eventTargetAddEventListener = EventTarget.prototype.addEventListener var documentRemoveEventListener = document.removeEventListener var eventTargetRemoveEventListener = EventTarget.prototype.removeEventListener var events = [] /** * Used to save the monitored event information */ class Event { constructor(el, type, listener, useCapture) { this.el = el this.type = type this.listener = listener this.useCapture = useCapture } } /** * Custom add event listener function * @param {String} type Event type * @param {EventListener} listener Event listening function * @param {Boolean} {useCapture} Whether to capture event bubbles, default to false */ function addEventListener(type, listener, useCapture = false) { var _this = this var $addEventListener = _this === document ? documentAddEventListener : eventTargetAddEventListener events.push(new Event(_this, type, listener, useCapture)) $addEventListener.apply(this, arguments) } /** * Custom delete event functions by type * This method will delete all listening functions of this type, regardless of the number * @param {String} type Event type */ function removeEventListenerByType(type) { var _this = this var $removeEventListener = _this === document ? documentRemoveEventListener : eventTargetRemoveEventListener var removeIndexs = events .map((e, i) => (e.el === _this || e.type === arguments[0] ? i : -1)) .filter(i => i !== -1) removeIndexs.forEach(i => { var e = events[i] $removeEventListener.apply(e.el, [e.type, e.listener, e.useCapture]) }) removeIndexs.sort((a, b) => b - a).forEach(i => events.splice(i, 1)) } function clearEvent() { var eventTypes = [ 'copy', 'cut', 'select', 'contextmenu', 'selectstart', 'dragstart', ] document.querySelectorAll('*').forEach(el => { eventTypes.forEach(type => el.removeEventListenerByType(type)) }) } ;(function() { document.addEventListener = EventTarget.prototype.addEventListener = addEventListener document.removeEventListenerByType = EventTarget.prototype.removeEventListenerByType = removeEventListenerByType })() window.onload = function() { clearEvent() } })()
Finally, there are many JavaScript hook skills. It's true that writing Greasemonkey scripts is a lot used (๑ > ᴗ < ๑)