Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get notified about changes of the history via history.pushState?

People also ask

Which event is fired when the history of the browser window changes?

The popstate event of the Window interface is fired when the active history entry changes while the user navigates the session history.

Does history pushState reload page?

But this function is not intended to reload the browser. All the function does, is to add (push) a new "state" onto the browser history, so that in future, the user will be able to return to this state that the web-page is now in.

What does Window history replaceState do?

The History. replaceState() method modifies the current history entry, replacing it with the state object and URL passed in the method parameters. This method is particularly useful when you want to update the state object or URL of the current history entry in response to some user action.


5.5.9.1 Event definitions

The popstate event is fired in certain cases when navigating to a session history entry.

According to this, there is no reason for popstate to be fired when you use pushState. But an event such as pushstate would come in handy. Because history is a host object, you should be careful with it, but Firefox seems to be nice in this case. This code works just fine:

(function(history){
    var pushState = history.pushState;
    history.pushState = function(state) {
        if (typeof history.onpushstate == "function") {
            history.onpushstate({state: state});
        }
        // ... whatever else you want to do
        // maybe call onhashchange e.handler
        return pushState.apply(history, arguments);
    };
})(window.history);

Your jsfiddle becomes:

window.onpopstate = history.onpushstate = function(e) { ... }

You can monkey-patch window.history.replaceState in the same way.

Note: of course you can add onpushstate simply to the global object, and you can even make it handle more events via add/removeListener


Finally found the "correct" way to do this! It requires adding a privilege to your extension and using the background page (not just a content script), but it does work.

The event you want is browser.webNavigation.onHistoryStateUpdated, which is fired when a page uses the history API to change the URL. It only fires for sites that you have permission to access, and you can also use a URL filter to further cut down on the spam if you need to. It requires the webNavigation permission (and of course host permission for the relevant domain(s)).

The event callback gets the tab ID, the URL that is being "navigated" to, and other such details. If you need to take an action in the content script on that page when the event fires, either inject the relevant script directly from the background page, or have the content script open a port to the background page when it loads, have the background page save that port in a collection indexed by tab ID, and send a message across the relevant port (from the background script to the content script) when the event fires.


I do this with simple proxy. This is an alternative to prototype

window.history.pushState = new Proxy(window.history.pushState, {
  apply: (target: any, thisArg: any, argArray?: any) => {
    // trigger here what you need
    return target.apply(thisArg, argArray);
  },
});

I used to use this:

var _wr = function(type) {
    var orig = history[type];
    return function() {
        var rv = orig.apply(this, arguments);
        var e = new Event(type);
        e.arguments = arguments;
        window.dispatchEvent(e);
        return rv;
    };
};
history.pushState = _wr('pushState'), history.replaceState = _wr('replaceState');

window.addEventListener('replaceState', function(e) {
    console.warn('THEY DID IT AGAIN!');
});

It's almost the same as galambalazs did.

It's usually overkill though. And it might not work in all browsers. (I only care about my version of my browser.)

(And it leaves a var _wr, so you might want to wrap it or something. I didn't care about that.)


In addition to other answers. Instead of storing the original function, we can use the History interface.

history.pushState = function()
{
    // ...

    History.prototype.pushState.apply(history, arguments);
}

You could bind to the window.onpopstate event?

https://developer.mozilla.org/en/DOM%3awindow.onpopstate

From the docs:

An event handler for the popstate event on the window.

A popstate event is dispatched to the window every time the active history entry changes. If the history entry being activated was created by a call to history.pushState() or was affected by a call to history.replaceState(), the popstate event's state property contains a copy of the history entry's state object.


Since you're asking about a Firefox addon, here's the code that I got to work. Using unsafeWindow is no longer recommended, and errors out when pushState is called from a client script after being modified:

Permission denied to access property history.pushState

Instead, there's an API called exportFunction which allows the function to be injected into window.history like this:

var pushState = history.pushState;

function pushStateHack (state) {
    if (typeof history.onpushstate == "function") {
        history.onpushstate({state: state});
    }

    return pushState.apply(history, arguments);
}

history.onpushstate = function(state) {
    // callback here
}

exportFunction(pushStateHack, unsafeWindow.history, {defineAs: 'pushState', allowCallbacks: true});