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.


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

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

};

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.


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

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)


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


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.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.