There's a way to block most Javascript: attach a MutationObserver at the very beginning of pageload, and whenever the document changes, if a <script> tag is found, remove it:
<script>
new MutationObserver(() => {
console.log('Mutation observer running...');
document.body.querySelectorAll('script').forEach(scr => scr.remove());
})
.observe(document.documentElement, { childList: true, subtree: true });
</script>
<script>
console.log('hi');
</script>
Javascript from inline handlers can still run, but luckily, inline Javascript isn't so common in comparison to <script> tags (since it's bad practice), and, usually, all the substantive Javascript will be in a <script> tag regardless, so inline Javascript will probably often simply throw an error due to referencing an undefined function.
Although subtree: true is significantly more expensive than other MutationObservers when elements are added dynamically, since there's (almost certainly) no Javascript running on the page, it shouldn't be an issue, especially once the page has fully loaded.
To remove inline handlers as well, check if the added element has on attributes, and remove them:
<script>
const cleanNode = (node) => {
if (node.nodeType !== 1) {
// Not an element node:
return;
}
if (node.matches('script')) {
node.remove();
}
[...node.attributes].forEach((attr) => {
if (attr.name.startsWith('on')) {
node.removeAttribute(attr.name);
}
});
};
new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
[...mutation.addedNodes].forEach(cleanNode);
});
})
.observe(document.documentElement, { childList: true, subtree: true });
</script>
<script>
console.log('hi');
</script>
<img onerror="alert('JS running')" src>