[javascript] Event when window.location.href changes

I'm writing a Greasemonkey script for a site which at some point modifies location.href.

How can I get an event (via window.addEventListener or something similar) when window.location.href changes on a page? I also need access to the DOM of the document pointing to the new/modified url.

I've seen other solutions which involve timeouts and polling, but I'd like to avoid that if possible.

This question is related to javascript dom greasemonkey dom-events

The answer is


There is a default onhashchange event that you can use.

Documented HERE

And can be used like this:

function locationHashChanged( e ) {
    console.log( location.hash );
    console.log( e.oldURL, e.newURL );
    if ( location.hash === "#pageX" ) {
        pageX();
    }
}

window.onhashchange = locationHashChanged;

If the browser doesn't support oldURL and newURL you can bind it like this:

//let this snippet run before your hashChange event binding code
if( !window.HashChangeEvent )( function() {
    let lastURL = document.URL;
    window.addEventListener( "hashchange", function( event ) {
        Object.defineProperty( event, "oldURL", { enumerable: true, configurable: true, value: lastURL } );
        Object.defineProperty( event, "newURL", { enumerable: true, configurable: true, value: document.URL } );
        lastURL = document.URL;
    } );
} () );

Have you tried beforeUnload? This event fires immediately before the page responds to a navigation request, and this should include the modification of the href.

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
    <HTML>
    <HEAD>
    <TITLE></TITLE>
    <META NAME="Generator" CONTENT="TextPad 4.6">
    <META NAME="Author" CONTENT="?">
    <META NAME="Keywords" CONTENT="?">
    <META NAME="Description" CONTENT="?">
    </HEAD>

         <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js" type="text/javascript"></script>
            <script type="text/javascript">
            $(document).ready(function(){
                $(window).unload(
                        function(event) {
                            alert("navigating");
                        }
                );
                $("#theButton").click(
                    function(event){
                        alert("Starting navigation");
                        window.location.href = "http://www.bbc.co.uk";
                    }
                );

            });
            </script>


    <BODY BGCOLOR="#FFFFFF" TEXT="#000000" LINK="#FF0000" VLINK="#800000" ALINK="#FF00FF" BACKGROUND="?">

        <button id="theButton">Click to navigate</button>

        <a href="http://www.google.co.uk"> Google</a>
    </BODY>
    </HTML>

Beware, however, that your event will fire whenever you navigate away from the page, whether this is because of the script, or somebody clicking on a link. Your real challenge, is detecting the different reasons for the event being fired. (If this is important to your logic)


Well there is 2 ways to change the location.href. Either you can write location.href = "y.html", which reloads the page or can use the history API which does not reload the page. I experimented with the first a lot recently.

If you open a child window and capture the load of the child page from the parent window, then different browsers behave very differently. The only thing that is common, that they remove the old document and add a new one, so for example adding readystatechange or load event handlers to the old document does not have any effect. Most of the browsers remove the event handlers from the window object too, the only exception is Firefox. In Chrome with Karma runner and in Firefox you can capture the new document in the loading readyState if you use unload + next tick. So you can add for example a load event handler or a readystatechange event handler or just log that the browser is loading a page with a new URI. In Chrome with manual testing (probably GreaseMonkey too) and in Opera, PhantomJS, IE10, IE11 you cannot capture the new document in the loading state. In those browsers the unload + next tick calls the callback a few hundred msecs later than the load event of the page fires. The delay is typically 100 to 300 msecs, but opera simetime makes a 750 msec delay for next tick, which is scary. So if you want a consistent result in all browsers, then you do what you want to after the load event, but there is no guarantee the location won't be overridden before that.

var uuid = "win." + Math.random();
var timeOrigin = new Date();
var win = window.open("about:blank", uuid, "menubar=yes,location=yes,resizable=yes,scrollbars=yes,status=yes");


var callBacks = [];
var uglyHax = function (){
    var done = function (){
        uglyHax();
        callBacks.forEach(function (cb){
            cb();
        });
    };
    win.addEventListener("unload", function unloadListener(){
        win.removeEventListener("unload", unloadListener); // Firefox remembers, other browsers don't
        setTimeout(function (){
            // IE10, IE11, Opera, PhantomJS, Chrome has a complete new document at this point
            // Chrome on Karma, Firefox has a loading new document at this point
            win.document.readyState; // IE10 and IE11 sometimes fails if I don't access it twice, idk. how or why
            if (win.document.readyState === "complete")
                done();
            else
                win.addEventListener("load", function (){
                    setTimeout(done, 0);
                });
        }, 0);
    });
};
uglyHax();


callBacks.push(function (){
    console.log("cb", win.location.href, win.document.readyState);
    if (win.location.href !== "http://localhost:4444/y.html")
        win.location.href = "http://localhost:4444/y.html";
    else
        console.log("done");
});
win.location.href = "http://localhost:4444/x.html";

If you run your script only in Firefox, then you can use a simplified version and capture the document in a loading state, so for example a script on the loaded page cannot navigate away before you log the URI change:

var uuid = "win." + Math.random();
var timeOrigin = new Date();
var win = window.open("about:blank", uuid, "menubar=yes,location=yes,resizable=yes,scrollbars=yes,status=yes");


var callBacks = [];
win.addEventListener("unload", function unloadListener(){
    setTimeout(function (){
        callBacks.forEach(function (cb){
            cb();
        });
    }, 0);
});


callBacks.push(function (){
    console.log("cb", win.location.href, win.document.readyState);
    // be aware that the page is in loading readyState, 
    // so if you rewrite the location here, the actual page will be never loaded, just the new one
    if (win.location.href !== "http://localhost:4444/y.html")
        win.location.href = "http://localhost:4444/y.html";
    else
        console.log("done");
});
win.location.href = "http://localhost:4444/x.html";

If we are talking about single page applications which change the hash part of the URI, or use the history API, then you can use the hashchange and the popstate events of the window respectively. Those can capture even if you move in history back and forward until you stay on the same page. The document does not changes by those and the page is not really reloaded.


I use this script in my extension "Grab Any Media" and work fine ( like youtube case )

var oldHref = document.location.href;

window.onload = function() {

    var
         bodyList = document.querySelector("body")

        ,observer = new MutationObserver(function(mutations) {

            mutations.forEach(function(mutation) {

                if (oldHref != document.location.href) {

                    oldHref = document.location.href;

                    /* Changed ! your code here */

                }

            });

        });

    var config = {
        childList: true,
        subtree: true
    };

    observer.observe(bodyList, config);

};

You can't avoid polling, there isn't any event for href change.

Using intervals is quite light anyways if you don't go overboard. Checking the href every 50ms or so will not have any significant effect on performance if you're worried about that.


Through Jquery, just try

$(window).on('beforeunload', function () {
    //your code goes here on location change 
});

By using javascript:

window.addEventListener("beforeunload", function (event) {
   //your code goes here on location change 
});

Refer Document : https://developer.mozilla.org/en-US/docs/Web/Events/beforeunload


Examples related to javascript

need to add a class to an element How to make a variable accessible outside a function? Hide Signs that Meteor.js was Used How to create a showdown.js markdown extension Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Summing radio input values How to execute an action before close metro app WinJS javascript, for loop defines a dynamic variable name Getting all files in directory with ajax

Examples related to dom

How do you set the document title in React? How to find if element with specific id exists or not Cannot read property 'style' of undefined -- Uncaught Type Error adding text to an existing text element in javascript via DOM Violation Long running JavaScript task took xx ms How to get `DOM Element` in Angular 2? Angular2, what is the correct way to disable an anchor element? React.js: How to append a component on click? Detect click outside React component DOM element to corresponding vue.js component

Examples related to greasemonkey

window.close and self.close do not close the window in Chrome Why doesn't document.addEventListener('load', function) work in a greasemonkey script? How do I get the information from a meta tag with JavaScript? Event when window.location.href changes alert a variable value Get image data url in JavaScript? How can I use jQuery in Greasemonkey?

Examples related to dom-events

Detecting real time window size changes in Angular 4 Does Enter key trigger a click event? What are passive event listeners? Stop mouse event propagation React onClick function fires on render How do you Hover in ReactJS? - onMouseLeave not registered during fast hover over iFrame onload JavaScript event addEventListener, "change" and option selection Automatically pass $event with ng-click? JavaScript click event listener on class