Annoying things you can do with JavaScript
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⌗
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⌗
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⌗
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?)⌗
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.