176 votes

Comment être notifié des changements de l'historique via history.pushState ?

Donc maintenant que HTML5 introduit history.pushState pour modifier l'historique du navigateur, les sites web commencent à l'utiliser en combinaison avec Ajax au lieu de changer l'identifiant du fragment de l'URL.

Malheureusement, cela signifie que ces appels ne peuvent plus être détectés par onhashchange .

Ma question est la suivante : Existe-t-il un moyen fiable (hack ? ;)) de détecter quand un site web utilise history.pushState ? La spécification ne dit rien sur les événements qui sont soulevés (du moins, je n'ai rien trouvé).
J'ai essayé de créer une façade et j'ai remplacé window.history avec mon propre objet JavaScript, mais cela n'a pas eu d'effet du tout.

Plus d'explications : Je suis en train de développer un module complémentaire pour 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 écouter des Événements DOM serait efficace mais je préfère ne pas m'y fier car ces événements peuvent être générés pour de nombreuses 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 à l'améliorer !).

Mise à jour 2 :

Un autre problème (secondaire) est que window.location n'est pas mis à jour lorsque l'on utilise pushState (mais je l'ai déjà lu ici sur SO je crois).

221voto

galambalazs Points 24393

5.5.9.1 Définitions des événements

El État de la population est déclenché dans certains cas lors de la navigation vers une entrée de l'historique de la session.

D'après ceci, il n'y a aucune raison pour que popstate soit déclenché lorsque 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 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 faire du bricolage window.history.replaceState de la même manière.

Note : vous pouvez bien sûr ajouter onpushstate simplement à l'objet global, et vous pouvez même lui faire gérer plus d'événements par l'intermédiaire de add/removeListener

0 votes

Vous avez dit que vous avez remplacé l'ensemble history l'objet. Cela peut être inutile dans ce cas.

0 votes

@galambalazs : Oui, probablement. Peut-être (je ne sais pas) window.history est en lecture seule mais les propriétés de l'objet historique ne le sont pas... Merci beaucoup pour cette solution :)

0 votes

Selon la spécification, il existe un événement "pagetransition", mais il semble qu'il ne soit pas encore implémenté.

11voto

CBHacking Points 897

J'ai enfin trouvé la "bonne" façon de faire ! Cela nécessite d'ajouter un privilège à votre extension et d'utiliser la page d'arrière-plan (pas seulement un contenu script), mais cela fonctionne.

L'événement que vous voulez est browser.webNavigation.onHistoryStateUpdated qui est déclenché lorsqu'une page utilise la fonction history API pour modifier l'URL. Il ne se déclenche que pour les sites auxquels vous êtes autorisé à accéder, et vous pouvez également utiliser un filtre d'URL pour réduire davantage le spam si nécessaire. Il requiert le webNavigation (et bien sûr l'autorisation de l'hôte pour le(s) domaine(s) concerné(s)).

Le callback de l'événement obtient l'ID de l'onglet, l'URL vers laquelle on "navigue" et d'autres détails de ce genre. Si vous avez besoin d'effectuer une action dans le script du contenu de cette page lorsque l'événement se déclenche, il faut soit injecter le script pertinent directement à partir de la page d'arrière-plan, soit faire en sorte que le script du contenu ouvre un fichier port à la page d'arrière-plan lorsqu'elle se charge, faire en sorte que la page d'arrière-plan enregistre ce port dans une collection indexée par l'ID de l'onglet, et envoyer un message à travers le port pertinent (de l'arrière-plan script au contenu script) lorsque l'événement se déclenche.

6voto

Je fais cela avec un simple proxy. C'est une alternative au prototype

window.history.pushState = new Proxy(window.history.pushState, {
  apply: (target: any, thisArg: any, argArray?: any) => {
    // trigger here what you need
    return target.apply(thisArg, argArray);
  },
});

5voto

Rudie Points 8975

J'avais l'habitude d'utiliser ça :

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 a fait.

Mais c'est généralement excessif. Et cela peut ne pas fonctionner dans tous les navigateurs. (Je ne me soucie que de la version de mon navigateur).

(Et cela laisse une var _wr donc tu pourrais vouloir l'emballer ou autre. Je ne me suis pas soucié de ça.)

1 votes

Quasiment identique à cette réponse

2voto

Doliman100 Points 21

En plus d'autres réponses. Au lieu de stocker la fonction originale, nous pouvons utiliser l'interface History.

history.pushState = function()
{
    // ...

    History.prototype.pushState.apply(history, arguments);
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X