Reputation: 148
I'm trying to make an extension for Safari that lets users block javascript per-site, using the new API. The API lets you inject a javascript file that will be loaded onto websites before any of the content, with not much functionality than regular javascript as if it was running on an HTML page. Is there a way to block all javascript using javascript?
The API tells me, vaguely, that "The injected scripts are executed before the webpage has been fully loaded, so you can take action as resources are added to the webpage." Is there some event I can hook into to catch resources being loaded, and block them?
Upvotes: 3
Views: 895
Reputation: 370879
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>
Upvotes: 3