- // ==UserScript==
- // @name A Universal Script to Re-Enable the Selection and Copying
- // @name:zh-TW A Universal Script to Re-Enable the Selection and Copying
- // @version 1.7.8
- // @description Enables select, right-click, copy and drag on pages that disable them. Enhanced Feature: Alt Key HyperLink Text Selection
- // @description:zh-TW 解除禁止復制、剪切、選擇文本、右鍵菜單的限制。破解鎖右鍵、文字複製、文字選取。增強功能:Alt鍵超連結文字選取。
- // @include /^https?\:\/\//
- // @grant none
- // @run-at document-start
- // @namespace https://greasyfork.org/users/371179
- // ==/UserScript==
- (function $$($) {
- 'use strict';
- if (document == null || !document.documentElement) return window.requestAnimationFrame($$); // this is tampermonkey bug?? not sure
- //console.log('script at', location)
-
- function isSupportAdvancedEventListener() {
- if ('_b1750' in $) return $._b1750
- var prop = 0;
- document.createAttribute('z').addEventListener('', null, {
- get passive() {
- prop++;
- },
- get once() {
- prop++;
- }
- });
- return ($._b1750 = prop == 2);
- }
-
- function isSupportPassiveEventListener() {
- if ('_b1750' in $) return $._b1750
- var prop = 0;
- document.createAttribute('z').addEventListener('', null, {
- get passive() {
- prop++;
- }
- });
- return ($._b1750 = prop == 1);
- }
-
- var getSelection = window.getSelection || Error()(),
- requestAnimationFrame = window.requestAnimationFrame || Error()(),
- getComputedStyle = window.getComputedStyle || Error()();
-
- $ = {
- utSelectionColorHack: 'msmtwejkzrqa',
- utTapHighlight: 'xfcklblvkjsj',
- utLpSelection: 'gykqyzwufxpz',
- ksFuncReplacerNonFalse: '___dqzadwpujtct___',
- ksEventReturnValue: ' ___ndjfujndrlsx___',
- ksSetData: '___rgqclrdllmhr___',
-
- mAlert_DOWN: function() {}, //dummy function in case alert replacement is not valid
- mAlert_UP: function() {}, //dummy function in case alert replacement is not valid
-
- isAnySelection: function() {
- var sel = getSelection();
- return !sel ? null : (typeof sel.isCollapsed == 'boolean') ? !sel.isCollapsed : (sel.toString().length > 0);
- },
-
- createCSSElement: function(cssStyle, container) {
- var css = document.createElement('style'); //slope: DOM throughout
- css.type = 'text/css';
- css.innerHTML = cssStyle;
- if (container) container.appendChild(css);
- return css;
- },
-
- createFakeAlert: function(_alert) {
- if (typeof _alert != 'function') return null;
-
- function alert(msg) {
- alert.__isDisabled__() ? console.log("alert msg disabled: ", msg) : _alert.apply(this, arguments);
- };
- alert.toString = () => "function alert() { [native code] }";
- return alert;
- },
-
- createFuncReplacer: function(originalFunc, pName, resFX) {
- resFX = function(ev) {
- var res = originalFunc.apply(this, arguments);
- if (!this || this[pName] != resFX) return res; // if this is null or undefined, or this.onXXX is not this function
- if (res === false) return; // return undefined when "return false;"
- originalFunc[$.ksFuncReplacerNonFalse] = true;
- this[pName] = originalFunc; // restore original
- return res;
- }
- resFX.toString = () => originalFunc.toString();
- return resFX;
- },
-
- listenerDisableAll: function(evt) {
- var elmNode = evt.target;
- while (elmNode && elmNode.nodeType > 0) { //i.e. HTMLDocument or HTMLElement
- var pName = 'on' + evt.type
- var f = elmNode[pName];
- if (typeof f == 'function' && f[$.ksFuncReplacerNonFalse] !== true) {
- var nf = $.createFuncReplacer(f, pName);
- nf[$.ksFuncReplacerNonFalse] = true;
- elmNode[pName] = nf;
- }
- elmNode = elmNode.parentNode;
- }
- },
-
- onceCssHighlightSelection: () => {
- if (document.documentElement.hasAttribute($.utLpSelection)) return;
- $.onceCssHighlightSelection = null
- var s = [...document.querySelectorAll('a,p,div,span,b,i,strong,li')].filter(elm => elm.childElementCount === 0); // randomly pick an element containing text only to avoid css style bug
- var elm = !s.length ? document.body : s[s.length >> 1];
- var selectionStyle = getComputedStyle(elm, ':selection');
- if (/^rgba\(\d+,\s*\d+,\s*\d+,\s*0\)$/.test(selectionStyle.getPropertyValue('background-color'))) document.documentElement.setAttribute($.utSelectionColorHack, "");
- var elmStyle = getComputedStyle(elm)
- if (/^rgba\(\d+,\s*\d+,\s*\d+,\s*0\)$/.test(elmStyle.getPropertyValue('-webkit-tap-highlight-color'))) document.documentElement.setAttribute($.utTapHighlight, "");
- },
-
- isCurrentClipboardDataReplaced: function(clipboardData) {
- var items = clipboardData ? clipboardData.items : null;
- if (items && items.length > 0) {
- for (var i = 0, l = items.length; i < l; i++) {
- if (items[i].type == 'text/plain') return true;
- }
- }
- return false;
- },
-
- replacementSetData: function(_setData, evt) {
- if (typeof _setData != 'function') return;
-
- function setData() {
- var res = _setData.apply(this, arguments);
- try {
- if (evt.clipboardData === this && this.setData === setData && evt.cancelable && evt.defaultPrevented === false) {
- if ($.isCurrentClipboardDataReplaced(evt.clipboardData)) {
- evt.preventDefault();
- if (evt.defaultPrevented === true) {
- this.setData = _setData;
- delete this[$.ksSetData];
- }
- }
- }
- } catch (e) {}
- return res;
- }
- setData.toString = () => _setData.toString();
- evt.clipboardData.setData = setData;
- evt.clipboardData[$.ksSetData] = _setData;
- },
-
-
- enableSelectClickCopy: function() {
-
- $.eyEvts = ['keydown', 'keyup', 'copy', 'contextmenu', 'select', 'selectstart', 'dragstart', 'beforecopy']; //slope: throughout
-
- function isDeactivePreventDefault(evt) {
- if ($.bypass) return false;
- var j = $.eyEvts.indexOf(evt.type);
- switch (j) {
- case 3:
- if (evt.target instanceof Element && (evt.target.textContent || "").trim().length === 0) return false; //exclude elements like video
- return true;
- case -1:
- return false;
- case 0:
- case 1:
- return (evt.keyCode == 67 && (evt.ctrlKey || evt.metaKey) && !evt.altKey && !evt.shiftKey && $.isAnySelection() === true);
- case 2:
-
- if (!('clipboardData' in evt && 'setData' in DataTransfer.prototype)) return true; // Event oncopy not supporting clipboardData
- // see the richtext hack in https://www.cleancss.com/css-beautify/
- // see https://developer.mozilla.org/zh-CN/docs/Web/API/Element/copy_event
- // see https://w3c.github.io/clipboard-apis/#widl-ClipboardEvent-clipboardData
-
- if ($.isCurrentClipboardDataReplaced(evt.clipboardData) == false) { //no replacement data
- if (!evt.clipboardData[$.ksSetData] && evt.cancelable && evt.defaultPrevented === false) $.replacementSetData(evt.clipboardData.setData, evt);
- return true;
- }
- var trimedSelectionText = getSelection().toString().trim()
- if (trimedSelectionText) {
- //there is replacement data and the selection is not empty
- console.log("copy event - clipboardData replacement is allowed and the selection is not empty", trimedSelectionText)
- return false;
- } else {
- //there is replacement data and the selection is empty
- return false;
- }
- break; // for js formatting only
-
- default:
- return true;
- }
- }
-
- Event.prototype.preventDefault = (function(f) {
- function preventDefault() {
- if (!isDeactivePreventDefault(this)) f.apply(this);
- }
- preventDefault.toString = () => f.toString();
- return preventDefault;
- })(Event.prototype.preventDefault);
-
- Object.defineProperty(Event.prototype, "returnValue", {
- get() {
- return $.ksEventReturnValue in this ? this[$.ksEventReturnValue] : true;
- },
- set(newValue) {
- if (!isDeactivePreventDefault(this) && newValue === false) this.preventDefault();
- this[$.ksEventReturnValue] = newValue;
- },
- enumerable: true,
- configurable: true
- });
-
- for (var i = 2, eventsCount = $.eyEvts.length; i < eventsCount; i++) {
- document.addEventListener($.eyEvts[i], $.listenerDisableAll, true); // Capture Event; passive:false; expected occurrence COMPLETELY before Target Capture and Target Bubble
- }
-
- var _alert = window.alert; //slope: temporary
- if (typeof _alert == 'function') {
- var _mAlert = $.createFakeAlert(_alert);
- if (_mAlert) {
- var clickBlockingTo = 0;
- _mAlert.__isDisabled__ = () => clickBlockingTo > +new Date;
- $.mAlert_DOWN = () => (clickBlockingTo = +new Date + 50);
- $.mAlert_UP = () => (clickBlockingTo = +new Date + 20);
- window.alert = _mAlert
- }
- }
-
- },
-
- lpCheckPointer: function(targetElm) {
- if (targetElm && targetElm.nodeType == 1 && targetElm.matches('*:hover')) {
- if (getComputedStyle(targetElm).getPropertyValue('cursor') == 'pointer' && targetElm.textContent) return true;
- }
- return false;
- },
-
- lpFullCancel: function(evt, toPreventDefault) {
- $.bypass = true;
- !toPreventDefault || evt.preventDefault()
- evt.stopPropagation();
- evt.stopImmediatePropagation();
- $.bypass = false;
- },
-
- lpMouseDown: function(evt) {
- $.lpMouseActive = 0;
- if (evt.altKey && !evt.ctrlKey && !evt.metaKey && !evt.shiftKey && evt.button === 0 && $.lpCheckPointer(evt.target)) {
- $.lpMouseActive = 1;
- $.lpFullCancel(evt, false);
- document.documentElement.setAttribute($.utLpSelection, '');
- }
- },
-
- lpMouseUp: function(evt) {
- if ($.lpMouseActive == 1) {
- $.lpMouseActive = 2;
- document.documentElement.removeAttribute($.utLpSelection);
- $.lpFullCancel(evt, false);
- if ($.onceCssHighlightSelection) window.requestAnimationFrame($.onceCssHighlightSelection);
- }
- },
-
- lpClick: function(evt) {
- if ($.lpMouseActive == 2) {
- $.lpFullCancel(evt, false);
- }
- },
-
- lpEnable: function() { // this is an optional feature for modern browser
- // the built-in browser feature has already disabled the default event behavior, the coding is just to ensure no "tailor-made behavior" occuring.
- document.addEventListener('mousedown', $.lpMouseDown, {
- capture: true,
- passive: true
- })
- document.addEventListener('mouseup', $.lpMouseUp, {
- capture: true,
- passive: true
- })
- document.addEventListener('click', $.lpClick, {
- capture: true,
- passive: true
- })
- },
-
- mainEnableScript: () => {
- var cssStyleOnReady = `
-
- *, body *, div, span, body *::before, body *::after, *:hover, *:link, *:visited, *:active , *[style], *[class]{
- -khtml-user-select: auto !important; -moz-user-select: auto !important; -ms-user-select: auto !important;
- -webkit-touch-callout: default !important; -webkit-user-select: auto !important; user-select: auto !important;
- }
- *:hover>img[src]{pointer-events:auto !important;}
-
- [${$.utSelectionColorHack}] :not(input):not(textarea)::selection{ background-color: Highlight !important; color: HighlightText !important;}
- [${$.utSelectionColorHack}] :not(input):not(textarea)::-moz-selection{ background-color: Highlight !important; color: HighlightText !important;}
- [${$.utTapHighlight}] *{ -webkit-tap-highlight-color: rgba(0, 0, 0, 0.18) !important;}
-
- html[${$.utLpSelection}] *:hover, html[${$.utLpSelection}] *:hover * { cursor:text !important;}
- html[${$.utLpSelection}] :not(input):not(textarea)::selection {background-color: rgba(255, 156, 179,0.5) !important;}
- html[${$.utLpSelection}] :not(input):not(textarea)::-moz-selection {background-color: rgba(255, 156, 179,0.5) !important;}
-
- [scc_emptyblock="2"]{pointer-events:none !important;user-select:none !important;}
- img[scc_emptyblock="4"]{
- opacity: 0 !important;
- padding: 0 !important;
- margin: 0 !important;
- position: absolute !important;
- width: 100% !important;
- height: 100% !important;
- left: 0 !important;
- top: 0 !important;
-
- pointer-events:auto; user-select:auto;cursor:inherit;}
-
- `.trim();
-
- $.enableSelectClickCopy()
- $.createCSSElement(cssStyleOnReady, document.documentElement);
-
- },
-
- mainEvents: (listenerPress, listenerRelease) => {
- document.addEventListener("mousedown", listenerPress, true); // Capture Event; (desktop)
- document.addEventListener("contextmenu", listenerPress, true); // Capture Event; (desktop&mobile)
- document.addEventListener("mouseup", listenerRelease, false); // Bubble Event;
- },
-
- disableHoverBlock: () => {
-
- var nMap = new WeakMap()
-
- function elmParam(elm) {
-
- var mElm = nMap.get(elm);
- if (!mElm) nMap.set(elm, mElm = {});
- return mElm;
- }
-
- function overlapArea(rect1, rect2) {
-
- let l1 = {
- x: rect1.left,
- y: rect1.top
- }
-
- let r1 = {
- x: rect1.right,
- y: rect1.bottom
- }
- let l2 = {
- x: rect2.left,
- y: rect2.top
- }
-
- let r2 = {
- x: rect2.right,
- y: rect2.bottom
- }
-
- // Area of 1st Rectangle
- let area1 = Math.abs(l1.x - r1.x) * Math.abs(l1.y - r1.y);
-
- // Area of 2nd Rectangle
- let area2 = Math.abs(l2.x - r2.x) * Math.abs(l2.y - r2.y);
-
- // Length of intersecting part i.e
- // start from max(l1.x, l2.x) of
- // x-coordinate and end at min(r1.x,
- // r2.x) x-coordinate by subtracting
- // start from end we get required
- // lengths
- let x_dist = Math.min(r1.x, r2.x) - Math.max(l1.x, l2.x);
- let y_dist = (Math.min(r1.y, r2.y) - Math.max(l1.y, l2.y));
- let areaI = 0;
- if (x_dist > 0 && y_dist > 0) {
- areaI = x_dist * y_dist;
- }
-
- return {
- area1,
- area2,
- areaI
- };
-
-
- }
-
- function redirectEvent(event, toElement) {
-
- toElement.dispatchEvent(new event.constructor(event.type, event));
- event.preventDefault();
- event.stopPropagation();
- }
-
- const floatingBlockHover = new WeakMap();
-
- document.addEventListener('mouseenter', function(evt) {
-
- if (evt && evt.target && evt.target.parentNode) {} else {
- return;
- }
-
- if (floatingBlockHover.has(evt.target)) return;
-
- function setNULL() {
- floatingBlockHover.set(evt.target, 1)
- }
-
- if (evt.target.nodeType != 1) return setNULL();
- if ("SVG|IMG|HTML|BODY".indexOf(evt.target.nodeName) >= 0) return setNULL();
-
- const targetArea = evt.target.clientWidth * evt.target.clientHeight
-
- if (targetArea > 0) {} else {
- return setNULL();
- }
-
- let possibleResults = [];
-
- for (const imgElm of document.querySelectorAll('img[src]')) {
- const param = elmParam(imgElm)
- if (!param.area) {
- const area = imgElm.clientWidth * imgElm.clientHeight
- if (area > 0) param.area = area;
- }
- if (param.area > 0) {
- if (targetArea > param.area*0.9) possibleResults.push(imgElm)
- }
- }
-
- let i = 0;
- let j = 0;
- for (const imgElm of possibleResults) {
-
- const cmpVal = evt.target.compareDocumentPosition(imgElm)
-
- /*
-
-
- 1: The two nodes do not belong to the same document.
- 2: p1 is positioned after p2.
- 4: p1 is positioned before p2.
- 8: p1 is positioned inside p2.
- 16: p2 is positioned inside p1.
- 32: The two nodes has no relationship, or they are two attributes on the same element.
-
- */
-
- if (cmpVal & 8 || cmpVal & 16) return setNULL();
- if (cmpVal & 2) j++; // I<p
- else if (cmpVal & 4) break; // I>p
-
-
- i++;
-
- }
-
- // before: j-1 after: j
-
- let indexBefore = j - 1;
- let indexAfter = j;
- if (indexBefore < 0) indexBefore = 0;
- if (indexAfter > possibleResults.length - 1) indexAfter = possibleResults.length - 1;
-
- for (let i = indexBefore; i <= indexAfter; i++) {
- const s = possibleResults[i];
- const {
- area1,
- area2,
- areaI
- } = overlapArea(evt.target.getBoundingClientRect(), s.getBoundingClientRect())
- const criteria = area1 * 0.7
- if (areaI>0.9*area2) {
- setNULL();
- let nImg=document.createElement('img');
- nImg.setAttribute('scc_emptyblock','4');
- evt.target.insertBefore(nImg,evt.target.firstChild);
- nImg.setAttribute('src',s.getAttribute('src'));
- evt.target.setAttribute('scc_emptyblock', '7');
- !(function(hElm, imgElm){
- nImg.addEventListener('click', function(event){ if(event.button!=2)redirectEvent(event, hElm) }, true);
- nImg.addEventListener('mousedown', function(event){ if(event.button!=2)redirectEvent(event, hElm) }, true);
- nImg.addEventListener('mouseup', function(event){ if(event.button!=2)redirectEvent(event, hElm) }, true);
- })(evt.target, s)
- return;
- }
- }
-
- setNULL();
-
- }, isSupportPassiveEventListener() ? {
- passive: true,
- capture: true
- } : true)
-
- }
-
- }
-
- $.mainEnableScript();
-
- if (isSupportAdvancedEventListener()) $.lpEnable(); // top capture event for alt-click
-
- $.mainEvents(
- function(evt) {
- if ($.onceCssHighlightSelection) window.requestAnimationFrame($.onceCssHighlightSelection);
- if (evt.button == 2 || evt.type == "contextmenu") $.mAlert_DOWN();
- },
- function(evt) {
- if (evt.button == 2) $.mAlert_UP();
- }
- );
-
- $.disableHoverBlock();
-
- console.log('userscript running - To Re-Enable Selection & Copying');
-
-
-
-
- })();