3

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?

4
  • uMatrix already does something similar for Firefox, so you can draw inspiration from it. Commented Nov 22, 2019 at 23:10
  • There is also another FF extension called JavaScript Toggle On and Off Commented Nov 22, 2019 at 23:16
  • I use uMatrix, and it's not on safari, which is why I'm trying to write this extension :P. I think uMatrix uses some FF apis anyways. Commented Nov 22, 2019 at 23:18
  • Wow, this is interesting. I didn't even think about this sort of thing as an option before, but since the MutationObserver appears to work (in almost all cases), I'll probably be using this technique a lot with userscripts, it's quite handy. Commented Nov 22, 2019 at 23:27

1 Answer 1

3

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>

Sign up to request clarification or add additional context in comments.

1 Comment

Just to be clear on the limitations, this will block JS that is going to be added as script tags <button onclick="alert('but not inline JavaScript')">Click me!</button>

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.