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