Enables text selection and copying on websites that block it using CSS or JavaScript
// ==UserScript==
// @name Enable Text Select and Copy
// @namespace RW5hYmxlIFRleHQgU2VsZWN0IGFuZCBDb3B5
// @version 1.5
// @description Enables text selection and copying on websites that block it using CSS or JavaScript
// @author smed79
// @license GPLv3
// @icon https://i25.servimg.com/u/f25/11/94/21/24/select10.png
// @match *://*/*
// @exclude https://*.proton.me/*
// @exclude https://*.github.com/*
// @exclude https://github.com/*
// @run-at document-start
// @grant none
// ==/UserScript==
(function() {
'use strict';
/**
* 1. INJECT HIGH-PERFORMANCE CSS
* This handles 90% of copy-blocking natively without slow JS loops.
* It excludes form controls so we don't break website interactivity.
*/
const injectCSS = () => {
const style = document.createElement('style');
style.textContent = `
/* Force default behavior on everything EXCEPT interactive elements */
*:not(input):not(textarea):not(select):not(button):not(a):not([contenteditable="true"]):not([role="button"]):not([role="textbox"]):not([role="combobox"]):not([role="slider"]):not([role="spinbutton"]) {
user-select: auto !important;
-webkit-user-select: auto !important;
-webkit-touch-callout: default !important;
}
/* Force text selection on common text containers */
p, h1, h2, h3, h4, h5, h6, span, div, article, section, main, td, th, li, blockquote {
user-select: text !important;
-webkit-user-select: text !important;
}
`;
// Inject as early as possible
if (document.documentElement) {
document.documentElement.appendChild(style);
} else {
document.addEventListener('DOMContentLoaded', () => document.documentElement.appendChild(style));
}
};
/**
* Check if an element should be left alone (inputs, buttons, etc)
*/
const isInteractive = (el) => {
if (!el || !el.tagName) return true;
const tag = el.tagName.toUpperCase();
if (['INPUT', 'TEXTAREA', 'SELECT', 'BUTTON', 'OPTION'].includes(tag)) return true;
if (el.isContentEditable) return true;
return false;
};
/**
* 2. DEFEAT JAVASCRIPT BLOCKERS (Capture Phase Interception)
* By setting the third argument to 'true', our listener runs BEFORE the website's listeners.
* Calling stopPropagation() ensures the website's script never even sees the copy/click event.
*/
const interceptEvents = () => {
const eventsToLiberate = ['contextmenu', 'selectstart', 'copy', 'cut', 'paste', 'dragstart'];
eventsToLiberate.forEach(eventType => {
window.addEventListener(eventType, (e) => {
if (!isInteractive(e.target)) {
// Stop the event from bubbling down to the website's anti-copy scripts
e.stopPropagation();
// Note: We DO NOT call e.preventDefault() here.
// We WANT the browser's native default action (copying/selecting) to happen!
}
}, true); // `true` activates the Capture Phase
});
};
/**
* 3. CLEANUP INLINE HTML ATTRIBUTES
* Some sites use <body oncopy="return false;">.
* We run a very fast querySelector (much faster than looping over '*') to strip these.
*/
const cleanInlineAttributes = () => {
const blockingSelectors = '[oncontextmenu], [oncopy], [onselectstart], [ondragstart], [oncut]';
document.querySelectorAll(blockingSelectors).forEach(el => {
el.removeAttribute('oncontextmenu');
el.removeAttribute('oncopy');
el.removeAttribute('onselectstart');
el.removeAttribute('ondragstart');
el.removeAttribute('oncut');
});
};
// Initialize
injectCSS();
interceptEvents();
// Run inline attribute cleanup once the DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', cleanInlineAttributes);
} else {
cleanInlineAttributes();
}
})();