Rerun addon's script on URL dynamic change

To simplify the scenario, lets say I’m working on an extension that just contains: alert("Hello") whenever you load a page in example.com. Relevant manifest.json part:

"content_scripts": 
        [
            {
            "matches": ["*://*.example.com/*"],
            "js": ["script.js"]
            }
        ]

When I first visit the website, it works fine. But the problem is some of the links in the website don’t reload the page, they just manipulate the DOM. So a link for example will take you to example.com/foo, the script doesn’t run. Even when I return to the home page, it doesn’t run again, and all the edits that were made the first time are removed.

How do I make the add-on recognize that the page has changed, and rerun the script?

Single page application (SPA) doesn’t reload the page, so your script won’t be re-executed, but it’s still running!

So if you register some listeners, they will still fire even when user navigates around the page.
For example this will react to hash changes (if the SPA is using hash change):

window.addEventListener('hashchange', onHashChange, false)

Or something like this if it’s modifying the location:

After spending hours on this, I was finally able to achieve what I want, though not in the way I expected it would be. This is my solution:

 document.addEventListener("click", function(){
    editStuff();
});

This works just fine for the website I’m making the add-on for. There is some wasted computational power, as some clicks don’t really require the function to work again, but its minimal in my use case.

1 Like

I think I need to go back to something like this. I’ve tried popstate, but it doesn’t seem to fire:

window.addEventListener('popstate', function (event) {
  // Do stuff
});

And this one doesn’t work in Firefox and Safari:

window.navigation.addEventListener('navigate', (event) => {
  // Do stuff
})

But I guess I would need a separate listener to enter-key being pressed.

@Espen_Klem, unfortunately the History API’s popsate event only fires when the user navigates forward/back in history. It doesn’t fire in response to general navigation events. As you noted, Firefox and Safari don’t yet support the Navigation API, though both have signaled intent to support it (Firefox position, Safari position).

At the moment, I think the closest thing an extension can do is shim the History API methods in a MAIN world content script injected at document_start.

Be aware that monkey patching like this is generally frowned upon, but sometimes in the world of extensions you don’t have a better option. I should also highlight that I just threw this together for demo purposes and will most likely need a decent bit of iteration before its production ready.

manifest.json

{
  "name": "API shim example",
  "version": "0.1",
  "manifest_version": 3,
  "content_scripts": [{
    "matches": "*://discourse.mozilla.org/*",
    "js": ["content-shim.js"]
  }]
}

content-shim.js

// Execute our shim in an IIFE to avoid side effects in global scope
(() => {
  function handleNavigation(...args) {
    console.log("User navigated", args);
  }

  // Grab a reference to the original method
  const _pushState = History.prototype.pushState;

  // Replace the original method with a version that runs some custom
  // logic when pushState is called
  History.prototype.pushState = function pushState(state, _, url) {
    _pushState.call(this, state, _, url);
    handleNavigation(state, window.location);
  }

  window.addEventListener("popstate", (event) => {
    handleNavigation(state, event.target.location);
  });
})();

If you install this extension and click around on this site, you should see some “User navigated” messages getting logged to the console.

1 Like