Ereignis, wenn sich window.location.href ändert

82

Ich schreibe ein Greasemonkey-Skript für eine Site, die irgendwann geändert wird location.href.

Wie kann ich ein Ereignis (über window.addEventListeneroder ähnliches) erhalten, wenn window.location.hrefÄnderungen auf einer Seite vorgenommen werden? Ich benötige auch Zugriff auf das DOM des Dokuments, das auf die neue / geänderte URL verweist.

Ich habe andere Lösungen gesehen, die Zeitüberschreitungen und Abfragen beinhalten, aber ich möchte dies nach Möglichkeit vermeiden.

Johan Dahlin
quelle

Antworten:

86

Popstate-Ereignis :

Das Popstate-Ereignis wird ausgelöst, wenn sich der aktive Verlaufseintrag ändert. [...] Das Popstate-Ereignis wird nur durch eine Browseraktion ausgelöst, z. B. durch Klicken auf die Schaltfläche "Zurück" (oder durch Aufrufen von history.back () in JavaScript).

Das Abhören eines popstateEreignisses und das Senden eines popstateEreignisses bei Verwendung history.pushState()sollten also ausreichen, um Maßnahmen bei hrefÄnderungen zu ergreifen :

window.addEventListener('popstate', listener);

const pushUrl = (href) => {
  history.pushState({}, '', href);
  window.dispatchEvent(new Event('popstate'));
};
PHF
quelle
2
Aber wird der Popstate ausgelöst, bevor der Inhalt geladen wird?
user2782001
4
unbrauchbar, wenn Sie keine Kontrolle über den Code haben, derpushState
Nathan Gouy
2
Dies funktioniert nicht immer, es hängt davon ab, dass ein Popstate-Ereignis ausgelöst wird. Wenn Sie beispielsweise innerhalb von YouTube navigieren, wird dies nur ausgelöst, wenn Sie im Verlauf vorwärts oder rückwärts drücken
TrySpace
52

Ich benutze dieses Skript in meiner Erweiterung "Grab Any Media" und arbeite gut ( wie YouTube Fall )

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
quelle
Irgendwie mag ich diesen Ansatz ... fühlt sich ein wenig RxJs-ish an :)
Guntram
Die einzige Lösung, die für youtube hätte funktionieren können: [Nur Bericht] Es wurde abgelehnt, einen Worker aus ' youtube.com/sw.js ' zu erstellen , da dies gegen die folgende Richtlinie zur Inhaltssicherheitsrichtlinie verstößt: "worker-src 'none'".
Simon Meusel
MutationObserver
1
Ich habe sofort ohne Probleme für meinen Anwendungsfall gearbeitet, Gott segne dich! Es ist ärgerlich, dass es dafür noch kein natives Ereignis gibt (Popstate hat bei mir nicht funktioniert), aber eines Tages! Ich würde aber window.addEventListener("load", () => {})anstelle von window.onload verwenden :)
Sanchit Batra
Dies funktioniert, erkennt history.pushState auch im Erweiterungskontext
YangombiUmpakati
29

Sie können das Abrufen nicht vermeiden, es gibt kein Ereignis für eine Änderung von href.

Die Verwendung von Intervallen ist sowieso recht einfach, wenn Sie nicht über Bord gehen. Wenn Sie diesbezüglich etwa 50 ms lang die href überprüfen, hat dies keine wesentlichen Auswirkungen auf die Leistung.

Tatu Ulmanen
quelle
3
@AlexanderMills: Zum Glück gibt es diese neuen Lösungen von popstateund hashchange.
serv-inc
Das ist nicht wahr.
inf3rno
@ MartinZvarík Bereits 2010 war es möglich, das Entladeereignis sowie eine Mikro- oder Makroaufgabe zu verwenden, um das neue Dokument zu laden.
inf3rno
3
Leider ist diese Antwort im Jahr 2020 immer noch richtig. Popstate wird nur ausgelöst, wenn der Benutzer die Vorwärts- / Rückwärts-Schaltflächen verwendet, und Hash-Änderung wird nur für Hash-Änderungen ausgelöst. Sofern Sie nicht die Kontrolle über den Code haben, durch den sich der Speicherort ändert, müssen Sie 🤷‍♀️
Rico Kahler
12

Es gibt ein Standardereignis onhashchange, das Sie verwenden können.

HIER dokumentiert

Und kann so verwendet werden:

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

window.onhashchange = locationHashChanged;

Wenn der Browser dies nicht unterstützt oldURLund newURLSie ihn wie folgt binden können:

//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
quelle
8
Hashchange-Ereignisse werden nur gesendet, wenn Ihre URL einen Teil enthält, der mit einem # -Symbol beginnt, das normalerweise für Ankerlinks verwendet wird. Sonst wird es nicht ausgelöst.
Fredrik_Macrobond
1
window.addEventListener ("hashchange", funcRef, false);
Bishoy Hanna
7

Haben Sie es schon einmal versucht? Dieses Ereignis wird unmittelbar ausgelöst, bevor die Seite auf eine Navigationsanforderung reagiert. Dies sollte die Änderung der href beinhalten.

    <!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>

Beachten Sie jedoch, dass Ihr Ereignis immer dann ausgelöst wird, wenn Sie von der Seite weg navigieren, sei es aufgrund des Skripts oder wenn jemand auf einen Link klickt. Ihre eigentliche Herausforderung besteht darin, die verschiedenen Gründe für das Auslösen des Ereignisses zu ermitteln. (Wenn dies für Ihre Logik wichtig ist)

Belugabob
quelle
Ich bin mir nicht sicher, ob dies funktionieren wird, da die Hash-Änderungen häufig kein erneutes Laden der Seite beinhalten.
Samandmoore
Ich brauche auch Zugriff auf das neue DOM. Ich habe die Frage aktualisiert, um dies zu klären.
Johan Dahlin
OK - habe die hashSituation nicht berücksichtigt - werde darüber nachdenken. Wenn Sie auf das neue DOM zugreifen können, haben Sie kein Glück (was den Zugriff von einem Event-Handler betrifft), da das Ereignis ausgelöst wird, bevor das neue DOM geladen wurde. Möglicherweise können Sie die Logik in das Onload-Ereignis der neuen Seite integrieren, aber Sie haben möglicherweise dieselben Probleme bei der Identifizierung, ob Sie die Logik für jedes Laden dieser Seite ausführen müssen. Können Sie weitere Details zu dem, was Sie erreichen möchten, einschließlich des Seitenflusses, angeben?
Belugabob
Ich habe gerade versucht, auf die location.href im Ereignishandler onbeforeunload zuzugreifen, und sie zeigt die ursprüngliche URL, nicht die Ziel-URL.
CMCDragonkai
Vor dem Entladen kann das Entladen der Seite abgebrochen werden. Daher ist das Entladeereignis besser, wenn Sie die Navigation nicht abbrechen möchten.
Inf3rno
7

Durch JQuery , versuchen Sie einfach

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

Durch die Nutzung Javascript :

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

Siehe Dokument: https://developer.mozilla.org/en-US/docs/Web/Events/beforeunload

Praveen Danagoudra
quelle
Danke, das funktioniert bei mir.
Lwin Htoo Ko
2

Nun, es gibt zwei Möglichkeiten, das zu ändern location.href. Sie können entweder schreiben location.href = "y.html", wodurch die Seite neu geladen wird, oder Sie können die Verlaufs-API verwenden, die die Seite nicht neu lädt. Ich habe in letzter Zeit viel mit dem ersten experimentiert.

Wenn Sie ein untergeordnetes Fenster öffnen und die Last der untergeordneten Seite aus dem übergeordneten Fenster erfassen, verhalten sich verschiedene Browser sehr unterschiedlich. Das einzige, was häufig vorkommt, ist, dass das alte Dokument entfernt und ein neues hinzugefügt wird. Das Hinzufügen von Readystatechange- oder Load-Ereignishandlern zum alten Dokument hat also keine Auswirkungen. Die meisten Browser entfernen auch die Ereignishandler aus dem Fensterobjekt. Die einzige Ausnahme ist Firefox. In Chrome mit Karma Runner und in Firefox können Sie das neue Dokument im Ladebereitschaftsstatus erfassen, wenn Sie es verwendenunload + next tick. So können Sie beispielsweise einen Ladeereignishandler oder einen Readystatechange-Ereignishandler hinzufügen oder einfach protokollieren, dass der Browser eine Seite mit einem neuen URI lädt. In Chrome mit manuellen Tests (wahrscheinlich auch GreaseMonkey) und in Opera, PhantomJS, IE10, IE11 können Sie das neue Dokument nicht im Ladezustand erfassen. In diesen Browsern unload + next tickruft der Rückruf einige hundert ms später auf, als das Ladeereignis der Seite ausgelöst wird. Die Verzögerung beträgt normalerweise 100 bis 300 ms, aber die Oper simetime macht eine Verzögerung von 750 ms für den nächsten Tick, was beängstigend ist. Wenn Sie also in allen Browsern ein konsistentes Ergebnis erzielen möchten, tun Sie nach dem Ladeereignis, was Sie möchten. Es gibt jedoch keine Garantie dafür, dass der Speicherort zuvor nicht überschrieben wird.

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

Wenn Sie Ihr Skript nur in Firefox ausführen, können Sie eine vereinfachte Version verwenden und das Dokument in einem Ladezustand erfassen, sodass beispielsweise ein Skript auf der geladenen Seite nicht weg navigieren kann, bevor Sie die URI-Änderung protokollieren:

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

Wenn es sich um Einzelseitenanwendungen handelt, die den Hash-Teil des URI ändern oder die Verlaufs-API verwenden, können Sie das hashchangebzw. das popstateEreignis des Fensters verwenden. Diese können auch dann erfasst werden, wenn Sie sich im Verlauf vor und zurück bewegen, bis Sie auf derselben Seite bleiben. Das Dokument ändert sich dadurch nicht und die Seite wird nicht wirklich neu geladen.

inf3rno
quelle