Comment être notifié sur les changements de l'histoire par l'histoire.pushState?
si maintenant que HTML5 introduit history.pushState
pour changer l'histoire des navigateurs, les sites Web commencent à utiliser ce en combinaison avec Ajax au lieu de changer l'Identificateur de fragment de l'URL.
cela signifie malheureusement que ces appels ne peuvent plus être détectés par onhashchange
.
ma question Est: y a-t-il un moyen fiable (hack? ;)) pour détecter si un site web utilise des history.pushState
? Spécification ne dit rien au sujet des événements qui sont soulevés (au moins je n'ai rien trouvé).
J'ai essayé de créer une façade et remplacé window.history
par mon propre objet JavaScript, mais cela n'a eu aucun effet.
autre explication: je développe un Add-on de Firefox qui doit détecter ces changements et agir en conséquence.
Je sais qu'il y avait une question similaire il y a quelques jours qui demandait si l'écoute de certains événements DOM serait efficace, mais je préfère ne pas compter sur ce que ces événements peuvent être générés pour beaucoup de raisons différentes.
mise à jour:
voici un jsfiddle (utilisez Firefox 4 ou Chrome 8) qui montre que onpopstate
n'est pas déclenché lorsque pushState
est appelé (ou est-ce que je fais quelque chose de mal? N'hésitez pas à améliorer!).
mise à jour 2:
un autre problème (latéral) est que window.location
n'est pas mis à jour en utilisant pushState
(mais j'ai déjà lu à ce sujet ici sur Donc je pense).
8 réponses
5.5.9.1 définitions D'événements
l'événement popstate est déclenché dans certains cas lors de la navigation vers une entrée d'historique de session.
selon ceci, il n'y a aucune raison pour que popstate soit viré quand vous utilisez pushState
. Mais un événement tel que pushstate
serait utile. Parce que history
est un objet hôte, vous devriez faire attention avec lui, mais Firefox semble être bien dans ce cas. Ce code fonctionne très bien:
(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);
votre jsfiddle devient :
window.onpopstate = history.onpushstate = function(e) { ... }
Vous pouvez singe-patch window.history.replaceState
de la même manière.
Note: Bien sûr, vous pouvez ajouter onpushstate
simplement à l'objet global, et vous pouvez même le faire gérer plus d'événements via add/removeListener
vous pourriez vous lier à l'événement window.onpopstate
?
https://developer.mozilla.org/en/DOM%3awindow.onpopstate
à Partir de la documentation:
un gestionnaire d'événements pour le popstate événement sur la fenêtre.
un événement popstate est expédié à la fenêtre à chaque fois l'actif de l'histoire entrée changements. Si l'entrée de l'historique être activé a été créé par un appel pour l'histoire.pushState() ou a été affecté par un appel à l'histoire.replaceState(), la propriété d'état de l'événement popstate contient une copie de l'historique d'état de l'objet.
j'utilisais ceci:
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!');
});
C'est presque la même chose que galambalazs .
mais c'est souvent exagéré. Et il pourrait ne pas fonctionner dans tous les navigateurs. (Je ne se soucient ma version de mon navigateur.)
(et il laisse un var _wr
, donc vous pourriez vouloir l'envelopper ou quelque chose. Je n'ai pas de soins à ce sujet.)
je pense que ce sujet a besoin d'une solution plus moderne.
je suis sûr que nsIWebProgressListener
était dans le coin alors je suis surpris que personne ne l'ait mentionné.
D'un framescript (pour une compatibilité e10s):
let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebProgress);
webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_WINDOW | Ci.nsIWebProgress.NOTIFY_LOCATION);
puis écoute dans le onLoacationChange
onLocationChange: function onLocationChange(webProgress, request, locationURI, flags) {
if (flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT
qui va apparemment attraper tous les pushState. Mais il y a un commentaire qui avertit que cela "déclenche aussi pushState". Nous devons donc en faire filtrage plus là pour s'assurer que c'est juste pushstate choses.
et: resource://gre/modules/WebNavigationContent.js
puisque vous parlez d'un addon de Firefox, voici le code que j'ai obtenu pour travailler. Utiliser unsafeWindow
est n'est plus recommandé , et des erreurs se produisent lorsque pushState est appelé à partir d'un script client après avoir été modifié:
Permission refusée pour accéder à l'historique de la propriété.pushState
à la place, il y a une API appelée exportFunction qui permet à la fonction d'être injecté dans window.history
comme ceci:
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});
bien, je vois de nombreux exemples de remplacement de la propriété pushState
de history
mais je ne suis pas sûr que ce soit une bonne idée, je préférerais créer un événement de service basé sur une API similaire à l'histoire de cette façon vous pouvez contrôler non seulement push state mais replace state aussi et il ouvre des portes pour de nombreuses autres implémentations ne dépendant pas de global history API. Veuillez cocher l'exemple suivant:
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];
});
si vous avez besoin de la définition EventEmitter
, le code ci-dessus est basé sur L'émetteur d'événements NodeJS: https://github.com/nodejs/node/blob/36732084db9d0ff59b6ce31e839450cd91a156be/lib/events.js . utils.inherits
implémentation peut être trouvé ici: https://github.com/nodejs/node/blob/36732084db9d0ff59b6ce31e839450cd91a156be/lib/util.js#L970
réponse de galambalazs patches de singe window.history.pushState
et window.history.replaceState
, mais pour une raison quelconque, il a cessé de travailler pour moi. Voici une alternative qui n'est pas aussi agréable parce qu'elle utilise le sondage:
(function() {
var previousState = window.history.state;
setInterval(function() {
if (previousState !== window.history.state) {
previousState = window.history.state;
myCallback();
}
}, 100);
})();
comme la norme stipule:
notez que j'appelle l'histoire.pushState () ou history.replaceState () ne déclenchera pas un événement popstate. L'événement popstate n'est déclenché que par une action du navigateur, comme cliquer sur le bouton Précédent (ou appeler l'historique).retour () en JavaScript)
on doit appeler l'histoire.retour () à trigeer WindowEventHandlers.onpopstate
Ainsi, place de:
history.pushState(...)
:
history.pushState(...)
history.pushState(...)
history.back()