Comment être averti des changements de l'histoire via history.pushState?
-
14-10-2019 - |
Question
Alors, maintenant que HTML5 history.pushState
au changement l'histoire des navigateurs, sites commencent à utiliser ce en combinaison avec l'Ajax au lieu de changer l'identifiant de fragment de l'URL.
Malheureusement, cela signifie que ces appels ne peuvent pas être plus par onhashchange
détecter.
Ma question est: Y at-il un moyen fiable;) pour détecter lorsqu'un site utilise history.pushState
(Hack?)? La spécification ne rien d'état sur les événements qui sont élevés (au moins je ne pouvais pas trouver quoi que ce soit).
J'ai essayé de créer une façade et window.history
remplacé par mon propre objet JavaScript, mais cela n'a pas d'effet du tout.
Des explications plus détaillées. Je développe un add-on 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 ont demandé si l'écoute de certains événements DOM serait être efficace mais je me fierais plutôt pas parce 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
ne se déclenche pas quand pushState
est appelé (ou suis-je en train de faire quelque chose de mal? ne hésitez pas à l'améliorer!).
Mise à jour 2:
Un autre problème (côté) est que window.location
est pas mis à jour lors de l'utilisation pushState
(mais je lu à ce sujet déjà ici donc je pense).
La solution
5.5.9.1 définitions de l'événement
popstate événement est déclenché dans certains cas, lors de la navigation à une entrée d'historique de la session.
D'après cela, il n'y a aucune raison pour popstate être tiré lorsque vous utilisez pushState
. Mais un événement tel que pushstate
serait utile. Parce que history
est un objet hôte, vous devez être prudent avec elle, mais Firefox semble être gentil 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 window.history.replaceState
singe-patch de la même manière.
Remarque: Bien sûr, vous pouvez ajouter onpushstate
simplement à l'objet global, et vous pouvez même en faire gérer plus d'événements via add/removeListener
Autres conseils
Vous pourriez lier à l'événement window.onpopstate
?
https://developer.mozilla.org/en/DOM%3awindow.onpopstate
A partir de la documentation:
Un gestionnaire d'événement pour la popstate événement sur la fenêtre.
Un événement popstate est envoyé au fenêtre à chaque fois que l'histoire active changements d'entrée. Si l'entrée d'historique être activé a été créé par un appel à history.pushState () ou a été affectée par un appel à history.replaceState (), la propriété de l'Etat d'événement popstate contient une copie des années d'entrée de l'histoire objet Etat.
Je l'habitude d'utiliser 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!');
});
Il est presque le même que galambalazs fait.
Il est généralement exagéré cependant. Et il pourrait ne pas fonctionner dans tous les navigateurs. (Je me soucie seulement de ma version de mon navigateur.)
(Et il laisse un _wr
var, de sorte que vous pouvez envelopper ou quelque chose. Je ne tenais pas à ce sujet.)
enfin trouvé la façon de le faire « correct »! Il faut ajouter un privilège à votre poste et en utilisant la page d'arrière-plan (pas seulement un script contenu), mais il fonctionne.
L'événement que vous voulez est browser.webNavigation.onHistoryStateUpdated
, qui est déclenché lorsque une page utilise l'API history
pour modifier l'URL. Il tire uniquement pour les sites que vous avez la permission d'accès, et vous pouvez également utiliser un filtre d'URL de réduire davantage sur le spam si vous avez besoin. Il nécessite l'autorisation de webNavigation
(et de l'autorisation d'accueil de cours pour le domaine concerné (s)).
Le rappel d'événement obtient l'ID onglet, l'URL qui est « naviguait », et d'autres tels détails. Si vous devez prendre une action dans le script contenu sur cette page lorsque l'événement se déclenche, soit inject le script correspondant directement depuis la page d'arrière-plan, ou avoir le script contenu ouvrir un port
à la page d'arrière-plan quand il charge, ont la page d'arrière-plan sauver ce port dans une collection indexée par onglet ID, et envoyer un message à travers le port concerné (à partir du script d'arrière-plan du script de contenu) lorsque l'événement se déclenche.
(function() {
var previousState = window.history.state;
setInterval(function() {
if (previousState !== window.history.state) {
previousState = window.history.state;
myCallback();
}
}, 100);
})();
Puisque vous vous posez au sujet d'un addon Firefox, voici le code que je suis arrivé au travail. L'utilisation unsafeWindow
est pas plus recommandé , et les erreurs quand pushState est appelé à partir d'un script client après avoir été modifié:
Autorisation refusée à la propriété d'accès history.pushState
Au lieu de cela, il y a une API appelée exportFunction qui permet à la fonction qui doit être injectée dans window.history
comme suit:
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});
Je pense que ce sujet a besoin d'une solution plus moderne.
Je suis sûr nsIWebProgressListener
était autour de cette époque, je suis surpris personne ne l'a mentionné.
D'un framescript (pour e10s compatibilité):
let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebProgress);
webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_WINDOW | Ci.nsIWebProgress.NOTIFY_LOCATION);
Ensuite, écoute dans le onLoacationChange
onLocationChange: function onLocationChange(webProgress, request, locationURI, flags) {
if (flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT
Ce sera apparemment attraper de tous pushState. Mais il y a un avertissement de commentaires qu'il « déclenche aussi pour pushState ». Nous devons donc faire un peu plus de filtrage ici pour vous assurer qu'il est juste des choses pushState.
: ressources: //gre/modules/WebNavigationContent.js
Eh bien, je vois de nombreux exemples de remplacer la propriété pushState
de history
mais je ne suis pas sûr que c'est une bonne idée, je préfère créer un événement de service basé sur une API similaire à l'histoire de cette façon vous pouvez contrôler non seulement pousser l'État, mais remplacer l'état aussi bien et ses portes ouvertes pour beaucoup d'autres implémentations ne faisant pas appel à l'API de l'histoire mondiale. S'il vous plaît vérifier 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énement NodeJS: https://github.com/nodejs/node/blob/36732084db9d0ff59b6ce31e839450cd91a156be/lib/events.js . la mise en œuvre de utils.inherits
se trouve ici: https://github.com /nodejs/node/blob/36732084db9d0ff59b6ce31e839450cd91a156be/lib/util.js#L970
Comme norme indique:
Notez que simplement appeler history.pushState () ou history.replaceState () ne déclenche pas un événement popstate. L'événement popstate ne se déclenche en faisant une action du navigateur, comme en cliquant sur le bouton de retour (ou en appelant history.back () en JavaScript)
nous devons appeler history.back () pour trigeer WindowEventHandlers.onpopstate
insted de:
history.pushState(...)
faire:
history.pushState(...)
history.pushState(...)
history.back()