// ==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.7.3
// @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://gf.qytechs.cn/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);
}
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;}
`.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;
}
}
$.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();
}
);
console.log('userscript running - To Re-Enable Selection & Copying');
})();