webentwicklung-frage-antwort-db.com.de

Wie kann man sich über history.pushState über Änderungen der Historie informieren?

Nun, da HTML5 history.pushState zur Änderung des Browserverlaufs einführt, verwenden Websites dies in Kombination mit Ajax, anstatt die Fragmentkennung der URL zu ändern.

Leider bedeutet dies, dass diese Anrufe von onhashchange nicht mehr erkannt werden können.

Meine Frage ist: Gibt es einen zuverlässigen Weg (Hack?;)) Zu erkennen, wenn eine Website history.pushState verwendet? Die Spezifikation sagt nichts über Ereignisse aus, die ausgelöst werden (zumindest konnte ich nichts finden).
Ich habe versucht, eine Fassade zu erstellen, und window.history durch mein eigenes JavaScript-Objekt ersetzt, aber es hatte überhaupt keine Auswirkungen.

Weitere Erklärung: Ich entwickle ein Firefox-Add-On, das diese Änderungen erkennen und entsprechend handeln muss.
Ich weiß, vor einigen Tagen gab es eine ähnliche Frage, in der gefragt wurde, ob das Anhören einiger DOM-Ereignisse effizient wäre, aber ich möchte mich nicht darauf verlassen, da diese Ereignisse aus vielen verschiedenen Gründen erzeugt werden können.

Update:

Hier ist ein jsfiddle (benutze Firefox 4 oder Chrome 8), das zeigt, dass onpopstate nicht ausgelöst wird, wenn pushState aufgerufen wird (oder mache ich etwas falsch? Fühlen Sie sich frei, es zu verbessern!).

Update 2:

Ein anderes (seitliches) Problem ist, dass window.location nicht aktualisiert wird, wenn pushState verwendet wird (aber ich habe darüber bereits auf SO, denke ich) gelesen.

121
Felix Kling

5.5.9.1 Ereignisdefinitionen

Das popstate -Ereignis wird in bestimmten Fällen ausgelöst, wenn Sie zu einem Sitzungsverlaufseintrag navigieren.

Demnach gibt es keinen Grund, warum popstate ausgelöst wird, wenn Sie pushState verwenden. Aber eine Veranstaltung wie pushstate wäre hilfreich. Da history ein Host-Objekt ist, sollten Sie vorsichtig damit umgehen, aber Firefox scheint in diesem Fall Nizza zu sein. Dieser Code funktioniert gut:

(function(history){
    var pushState = history.pushState;
    history.pushState = function(state) {
        if (typeof history.onpushstate == "function") {
            history.onpushstate({state: state});
        }
        // ... whatever else you want to do
        // maybe call onhashchange e.handler
        return pushState.apply(history, arguments);
    };
})(window.history);

Ihr jsfiddle wird zu:

window.onpopstate = history.onpushstate = function(e) { ... }

Sie können window.history.replaceState auf dieselbe Weise mit einem Affen-Patch versehen.

Hinweis: Natürlich können Sie onpushstate einfach zum globalen Objekt hinzufügen. Sie können sogar weitere Ereignisse über add/removeListener verarbeiten

147
gblazex

Sie könnten an das window.onpopstate-Ereignis binden?

https://developer.mozilla.org/de/DOM%3awindow.onpopstate

Aus den Dokumenten:

Ein Event-Handler für den Popstate Veranstaltung am Fenster.

Ein Popstate-Ereignis wird an die .__ gesendet. Fenster jedes Mal die aktive Historie Eintrag ändert sich. Wenn der Verlaufseintrag aktiviert wurde, wurde durch einen Aufruf erstellt zu history.pushState () oder war betroffen durch einen Aufruf von history.replaceState (), Die State-Eigenschaft des Popstate-Ereignisses enthält eine Kopie des .__ des Verlaufseintrags. Zustandsobjekt.

3
stef

Ich habe das verwendet:

var _wr = function(type) {
    var orig = history[type];
    return function() {
        var rv = orig.apply(this, arguments);
        var e = new Event(type);
        e.arguments = arguments;
        window.dispatchEvent(e);
        return rv;
    };
};
history.pushState = _wr('pushState'), history.replaceState = _wr('replaceState');

window.addEventListener('replaceState', function(e) {
    console.warn('THEY DID IT AGAIN!');
});

Es ist fast dasselbe wie Galambalazs .

Es ist jedoch normalerweise übertrieben. Und es funktioniert möglicherweise nicht in allen Browsern. (Ich interessiere mich nur für meine Version meines Browsers.)

(Und es hinterlässt einen var _wr, so dass Sie es vielleicht einpacken wollen oder so. Das interessierte mich nicht.)

2
Rudie

Ich denke, dieses Thema braucht eine modernere Lösung.

Ich bin mir sicher, dass nsIWebProgressListener damals da war. Ich bin überrascht, dass es niemand erwähnt hat.

Aus einem Framescript (für die Kompatibilität mit E10s):

let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebProgress);
webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_WINDOW | Ci.nsIWebProgress.NOTIFY_LOCATION);

Dann hören Sie in der onLoacationChange

onLocationChange: function onLocationChange(webProgress, request, locationURI, flags) {
       if (flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT

Das wird offenbar alle PushState's fangen. Es gibt jedoch eine Kommentar-Warnung, dass es "auch für pushState auslöst". Daher müssen wir hier noch ein wenig filtern, um sicherzustellen, dass es sich nur um Pushstate handelt.

Basierend auf: https://github.com/jgraham/gecko/blob/55d8d9aa7311386ee2dabfccb481684c8920a527/toolkit/modules/addons/WebNavigation.jsm#L18

Und: resource: //gre/modules/WebNavigationContent.js

1
Noitidart

Nun, ich sehe viele Beispiele für das Ersetzen der pushState-Eigenschaft von history, aber ich bin nicht sicher, ob dies eine gute Idee ist. Ich würde es vorziehen, ein Serviceereignis zu erstellen, das auf einer ähnlichen API basiert wie auf der Historie, sodass Sie nicht nur den Push-Status, sondern auch den Status steuern können Ersetzen Sie auch state und es öffnet sich die Tür für viele andere Implementierungen, die sich nicht auf die API für das globale Protokoll verlassen. Bitte überprüfen Sie das folgende Beispiel:

function HistoryAPI(history) {
    EventEmitter.call(this);
    this.history = history;
}

HistoryAPI.prototype = utils.inherits(EventEmitter.prototype);

const prototype = {
    pushState: function(state, title, pathname){
        this.emit('pushstate', state, title, pathname);
        this.history.pushState(state, title, pathname);
    },

    replaceState: function(state, title, pathname){
        this.emit('replacestate', state, title, pathname);
        this.history.replaceState(state, title, pathname);
    }
};

Object.keys(prototype).forEach(key => {
    HistoryAPI.prototype = prototype[key];
});

Wenn Sie die EventEmitter-Definition benötigen, basiert der obige Code auf dem NodeJS-Ereignisemitter: https://github.com/nodejs/node/blob/36732084db9d0ff59b6ce31e839450cd91a156be/lib/events.js . Die utils.inherits-Implementierung finden Sie hier: https://github.com/nodejs/node/blob/36732084db9d0ff59b6ce31e839450cd91a156be/lib/util.js#L970

0
Victor Queiroz

Endlich den "richtigen" Weg gefunden! Sie müssen Ihrer Erweiterung ein Privileg hinzufügen und die Hintergrundseite verwenden (nicht nur ein Inhaltsskript), aber es funktioniert.

Das gewünschte Ereignis ist browser.webNavigation.onHistoryStateUpdated . Dieses Ereignis wird ausgelöst, wenn eine Seite die history-API zum Ändern der URL verwendet. Es wird nur für Websites ausgelöst, auf die Sie zugreifen dürfen, und Sie können auch einen URL-Filter verwenden, um den Spam weiter einzuschränken, falls dies erforderlich ist. Es erfordert die webNavigation-Berechtigung (und natürlich die Host-Berechtigung für die entsprechenden Domänen).

Der Ereignisrückruf erhält die Registerkarten-ID, die URL, zu der "navigiert" wird, und andere solche Details. Wenn Sie beim Auslösen des Ereignisses eine Aktion im Inhaltsskript auf dieser Seite durchführen müssen, fügen Sie entweder das entsprechende Skript direkt von der Hintergrundseite ein oder lassen Sie das Inhaltsskript beim Laden eine port der Hintergrundseite öffnen Speichern Sie diesen Port in einer durch die Tab-ID indizierten Sammlung und senden Sie eine Nachricht über den entsprechenden Port (vom Hintergrundskript zum Inhaltsskript), wenn das Ereignis ausgelöst wird.

0
CBHacking

Da Sie nach einem Firefox-Addon fragen, haben Sie hier den Code, den ich zur Arbeit brauche. Die Verwendung von unsafeWindow ist wird nicht mehr empfohlen und tritt beim Aufruf von pushState aus einem Clientskript nach der Änderung aus:

Die Berechtigung zum Zugriff auf die Eigenschaft "history.pushState" wurde verweigert

Stattdessen gibt es eine API namens exportFunction , mit der die Funktion wie folgt in window.history eingefügt werden kann:

var pushState = history.pushState;

function pushStateHack (state) {
    if (typeof history.onpushstate == "function") {
        history.onpushstate({state: state});
    }

    return pushState.apply(history, arguments);
}

history.onpushstate = function(state) {
    // callback here
}

exportFunction(pushStateHack, unsafeWindow.history, {defineAs: 'pushState', allowCallbacks: true});
0
nathancahill

galambalazs Antwort Affen-Patches window.history.pushState und window.history.replaceState, aber aus irgendeinem Grund funktionierte das für mich nicht mehr. Hier ist eine Alternative, die nicht so schön ist, weil sie Polling verwendet:

(function() {
    var previousState = window.history.state;
    setInterval(function() {
        if (previousState !== window.history.state) {
            previousState = window.history.state;
            myCallback();
        }
    }, 100);
})();
0
Flimm