These are a couple of annoying things people do with JavaScript. I made a Tampermonkey script that defeats most of them except for scroll jacking and a live website to test it’s effectiveness.

https://github.com/NotJoeMartinez/anti-annoyance-tampermonkey

https://annoying-js.pages.dev/

preventDefault and stopPropagation

The preventDefault() method of the Event interface is a source of a lot of intentional and unintentional annoyances. preventDefault is unintentionally annoying when it overwrites an action that the user intends to make in favor of custom functionality the developer has implemented. preventDefault is also used maliciously to prevent actions as a sort of weak DRM or to increase engagement.

A way to prevent preventDefault from running on an event is to have another event listener on that same event call stopPropagation, as the name suggests this will stop the event from triggering preventDefault. I don’t really understand how browsers handle The Order of Multiple Event Listeners on the same event but my guess would be that because Tampermonkey scripts are executed within an IIFE the calls to stopPropagation in my script take precedence.

Breaking Keyboard Shortcuts

Live example

Most of the time when preventDefault is used incorrectly it’s because the developer is trying to implement keyboard shortcuts and forgot to listen for meta keys before trigging the custom action. This is very common for video players that use 0-9 to jump to parts in the video, which prevents users from switching tabs with Meta + Number.

Cause of annoyance:

document.addEventListener('keydown', function(e) {
	const targetKeys = [
		'0', '1', '2', '3', '4', '5', '6', 
		'7', '8', '9' // etc...
	];
	if (targetKeys.includes(e.key)) {
		e.preventDefault();
		// some custom action. User be damned. 
	}

Fix with Tampermonkey:

const targetKeys = [
	'0', '1', '2', '3', '4', '5', '6',
	'7', '8', '9' // etc...
];
document.addEventListener('keydown', function(e) {
	if ((e.ctrlKey || e.metaKey) && targetKeys.includes(e.key)) {
		e.stopPropagation();
	}
}, true);

Preventing Copy

Live example

This one waits for the copy or cut event listers to fire and calls preventDefault() which as the name suggests, prevents the default action from happening.

Cause of annoyance:

<div id="no-copy">
	<p>
		Here is some very important information that I won't 
		let you copy for some reason. 
	</p>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
	const noCopyElem = document.getElementById('no-copy');
    noCopyElem.addEventListener('copy', function(e) {
        e.preventDefault();

		// optional snarky message to write to clipbaord
		let snarkyMessage = "At least pay me if you're going to steal. ";
		snarkyMessage += "https://buymeacoffee.com/notjoemartinez";
        e.clipboardData.setData('text/plain', snarkyMessage);
    });
});
</script>

Fix with Tamper monkey:

// included other clipboard events
['copy', 'cut', 'paste'].forEach(function(event) {
	document.addEventListener(event, function(e) {
		e.stopPropagation();
		return true;
	}, true);
});

Preventing Right Click

Live example

This one is often unintentionally implemented when trying to build custom context menus and leaving out the ‘copy’ option or by trying to track user clicks.

<div id="no-right-click">
	<p>
	Lorem ipsum dolor sit amet, consectetur adipiscing elit.
	</p>
</div>
<script>
const noRightClick = document.getElementById('no-right-click');
noRightClick.addEventListener('contextmenu', function(e) {
	e.preventDefault();
});
</script>

Fix with Tamper monkey:

document.addEventListener('contextmenu', function(e) {
	e.stopPropagation();
	return true;
}, true);

Preventing Back Click (FireFox Feature?)

Live example

This method takes advantage of the browsers Window.history by writing the current url to the users history stack every the event lister for the back button popstate is triggered.

(function() {
	window.history.pushState(null, null, window.location.href);
	window.addEventListener('popstate', function() {
		window.history.pushState(null, null, window.location.href);
	});
})();

Fix with Tamper monkey:

This one’s a bit more complicated because you can’t just drop the event. You need to simulate a valid back click and calls to pushState are rate limited.

let backAttempts = 0;
const MAX_BACK_ATTEMPTS = 3;
const RESET_INTERVAL = 5000;

const originalPushState = history.pushState;
history.pushState = function() {
	// do nothing
};

window.addEventListener('popstate', function(event) {
	backAttempts++;
	
	if (backAttempts <= MAX_BACK_ATTEMPTS) {
		history.go(-1);
	} else {
		window.location.href = document.referrer || '/';
	}
});

setInterval(() => {
	backAttempts = 0;
}, RESET_INTERVAL);

originalPushState.call(history, null, null, window.location.href);

Scroll Jacking

Live Example (It’s a copy of this page from the Texas DMV website with scroll jacking implemented)

Scroll jacking has its place, however it’s often used where it shouldn’t be. Specifically on sites where the users could benefit from being able to quickly navigate to areas of the for information.

General cause of issue but there’s more:

window.addEventListener('wheel', (e) => {
	e.preventDefault();
	// bro thinks he's apple
}

I haven’t figured out how to defeat scroll jacking but most of the time it can be done with your browsers reader mode.