JavaScript event listener for history.pushState

2018-01-13 20:14:38 Coding JavaScript

In HTML there is an event onpopstate, but not onpushstate or onalterstate. How can we monitor such state changes? The answer is to alter the history.pushState function and manually dispatch an event inside.

More over, suppose we are writing a Chrome extension which has run_at: "document_start" that aims to capture all changes in states. Then we need to inject a <script> tag into the HTML, and we must do it as early as possible – right after the <head> tag is available. To achieve this we use a MutationObserver.

'use strict';

var observer = null;

function insertScript() {
    // script adapted from https://stackoverflow.com/a/25673911
    var s = '';
    s += 'var _wr = function(type) { var orig = history[type]; ';
    s += 'return function() { var rv = orig.apply(this, arguments);';
    s += 'var e = new Event(type); e.arguments = arguments; window.dispatchEvent(e);';
    s += 'return rv; }; };';
    s += 'history.pushState = _wr("pushState");';
    s += 'console.log("pushState altered")';

    var se = document.createElement('script');
    var head = document.getElementsByTagName('head')[0];
    se.innerHTML = s;
    head.insertBefore(se, null);
}

function observeHead() {
    observer = new MutationObserver(function(list) {
        for (var mutation of list) {
            if (mutation.addedNodes.length === 0) continue;
            var node = mutation.addedNodes[0];
            if (node.tagName !== 'HEAD') continue;

            observer.disconnect();
            insertScript();
            break;
        }
    });

    observer.observe(document, { childList: true, subtree: true });
}

observeHead();

window.addEventListener('pushState', function(ev) {
    console.log('pushState!');
});

The code for altering the pushState function comes from this StackOverflow answer