Evento cuando cambia window.location.href

83

Estoy escribiendo un script de Greasemonkey para un sitio que en algún momento modifica location.href.

¿Cómo puedo obtener un evento (vía window.addEventListenero algo similar) cuando window.location.hrefcambia en una página? También necesito acceso al DOM del documento que apunta a la URL nueva / modificada.

He visto otras soluciones que implican tiempos de espera y encuestas, pero me gustaría evitar eso si es posible.

Johan Dahlin
fuente

Respuestas:

87

evento popstate :

El evento popstate se activa cuando cambia la entrada activa del historial. [...] El evento popstate solo se activa al realizar una acción del navegador, como hacer clic en el botón Atrás (o llamar a history.back () en JavaScript)

Por lo tanto, escuchar el popstateevento y enviar un popstateevento cuando se usa history.pushState()debería ser suficiente para tomar medidas sobre el hrefcambio:

window.addEventListener('popstate', listener);

const pushUrl = (href) => {
  history.pushState({}, '', href);
  window.dispatchEvent(new Event('popstate'));
};
PHF
fuente
2
Pero, ¿se disparará el popstate antes de que se cargue el contenido?
user2782001
4
inutilizable si no tienes ningún control sobre el fragmento de código quepushState
Nathan Gouy
2
Esto no siempre funciona, depende de que se active un evento popstate. Por ejemplo, navegar dentro de YouTube no se activará, solo si presiona hacia atrás o hacia adelante en el historial
TrySpace
53

Utilizo este script en mi extensión "Grab Any Media" y funciona bien ( como en el caso de youtube )

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);

};
Leonardo Ciaccio
fuente
De alguna manera, me gusta este enfoque ... se siente un poco RxJs-ish :)
Guntram
La única solución que podría haber funcionado para youtube: [Solo informe] Se negó a crear un trabajador de ' youtube.com/sw.js ' porque viola la siguiente directiva de Política de seguridad de contenido: "worker-src 'none'".
Simon Meusel
MutationObserver
1
Trabajó desde el primer momento sin problemas para mi caso de uso, ¡Dios los bendiga! Es molesto que aún no haya un evento nativo para esto (popstate no funcionó para mí), ¡pero un día! Sin window.addEventListener("load", () => {})embargo, usaría en lugar de window.onload :)
Sanchit Batra
esto funciona, detecta history.pushState incluso en el contexto de extensión
YangombiUmpakati
29

No puede evitar el sondeo, no hay ningún evento para el cambio de href.

De todos modos, el uso de intervalos es bastante ligero si no se excede. Verificar el href cada 50 ms aproximadamente no tendrá ningún efecto significativo en el rendimiento si eso le preocupa.

Tatu Ulmanen
fuente
3
@AlexanderMills: afortunadamente existen estas nuevas soluciones de popstatey hashchange.
serv-inc
Esto no es verdad.
inf3rno
@ MartinZvarík Incluso en 2010 fue posible utilizar el evento de descarga más una tarea micro o macro para cargar el nuevo documento.
inf3rno
3
Desafortunadamente, esta respuesta sigue siendo correcta en 2020. popstate solo se activa cuando el usuario usa los botones de avance / retroceso y hashchange solo se activa para cambios de hash. A menos que tenga control sobre el código que está causando que cambie la ubicación, debe sondear 🤷‍♀️
Rico Kahler
12

Hay un onhashchangeevento predeterminado que puede utilizar.

Documentado AQUÍ

Y se puede usar así:

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

window.onhashchange = locationHashChanged;

Si el navegador no es compatible oldURLy newURLpuede vincularlo así:

//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;
    } );
} () );
iSkore
fuente
8
Los eventos Hashchange solo se envían si su URL tiene una parte que comienza con un símbolo '#', que normalmente se usa para enlaces de anclaje. De lo contrario, no disparará.
Fredrik_Macrobond
1
window.addEventListener ("hashchange", funcRef, false);
Bishoy Hanna
7

¿Has probado antes de descargar? Este evento se activa inmediatamente antes de que la página responda a una solicitud de navegación, y esto debería incluir la modificación del 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>

Sin embargo, tenga en cuenta que su evento se activará cada vez que navegue fuera de la página, ya sea por el script o porque alguien hace clic en un enlace. Tu verdadero desafío, es detectar las diferentes razones por las que el evento está siendo despedido. (Si esto es importante para tu lógica)

belugabob
fuente
No estoy seguro de si esto funcionará porque a menudo los cambios de hash no implican una recarga de página.
samandmoore
También necesito acceso al nuevo DOM, actualicé la pregunta para aclarar eso.
Johan Dahlin
Está bien, no consideró la hashsituación, pensará en eso. En cuanto a poder acceder al nuevo DOM, entonces no tiene suerte (en lo que respecta a acceder a esto desde un controlador de eventos) ya que el evento se activará antes de que se cargue el nuevo DOM. Es posible que pueda incorporar la lógica en el evento de carga de la nueva página, pero puede tener los mismos problemas con respecto a identificar si necesita llevar a cabo la lógica para cada carga de esa página. ¿Puede proporcionar más detalles sobre lo que está tratando de lograr, incluido el flujo de la página?
belugabob
Intenté acceder a location.href dentro del controlador de eventos onbeforeunload, y muestra la URL original, no la URL de destino.
CMCDragonkai
Beforeunload puede cancelar la descarga de la página, por lo que el evento de descarga es mejor si no desea cancelar la navegación.
inf3rno
7

A través de Jquery , solo intenta

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

Usando javascript :

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

Consulte el documento: https://developer.mozilla.org/en-US/docs/Web/Events/beforeunload

Praveen Danagoudra
fuente
Gracias, eso funciona para mí.
Lwin Htoo Ko
2

Bueno, hay 2 formas de cambiar el location.href. O puede escribir location.href = "y.html", lo que recarga la página o puede usar la API de historial que no recarga la página. Experimenté mucho con el primero recientemente.

Si abre una ventana secundaria y captura la carga de la página secundaria desde la ventana principal, los diferentes navegadores se comportan de manera muy diferente. Lo único que es común, que eliminan el documento antiguo y agregan uno nuevo, por ejemplo, agregar readystatechange o cargar controladores de eventos al documento anterior no tiene ningún efecto. La mayoría de los navegadores también eliminan los controladores de eventos del objeto de la ventana, la única excepción es Firefox. En Chrome con Karma Runner y en Firefox puedes capturar el nuevo documento en el estado de carga readyState si usasunload + next tick. Por lo tanto, puede agregar, por ejemplo, un controlador de eventos de carga o un controlador de eventos readystatechange o simplemente registrar que el navegador está cargando una página con un nuevo URI. En Chrome con pruebas manuales (probablemente GreaseMonkey también) y en Opera, PhantomJS, IE10, IE11 no puede capturar el nuevo documento en el estado de carga. En esos navegadores, las unload + next tickllamadas a la devolución de llamada unos cientos de ms después de que se activa el evento de carga de la página. El retraso es típicamente de 100 a 300 ms, pero opera simetime hace un retraso de 750 ms para el siguiente tic, lo cual da miedo. Entonces, si desea un resultado consistente en todos los navegadores, haga lo que quiera después del evento de carga, pero no hay garantía de que la ubicación no se anule antes de eso.

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";

Si ejecuta su script solo en Firefox, entonces puede usar una versión simplificada y capturar el documento en un estado de carga, por ejemplo, un script en la página cargada no puede navegar antes de registrar el cambio de URI:

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";

Si estamos hablando de aplicaciones de una sola página que cambian la parte hash de la URI, o usan la API del historial, entonces puede usar hashchangelos popstateeventos y de la ventana respectivamente. Esos pueden capturar incluso si se mueve en el historial hacia atrás y hacia adelante hasta que permanezca en la misma página. El documento no cambia por esos y la página realmente no se vuelve a cargar.

inf3rno
fuente