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).

115
demandé sur pnuts 2010-12-31 15:31:42

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

142
répondu galambalazs 2017-06-19 23:34:51

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.

3
répondu stef 2011-01-02 23:18:10

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.)

1
répondu Rudie 2017-05-23 12:34:39

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.

basé sur: https://github.com/jgraham/gecko/blob/55d8d9aa7311386ee2dabfccb481684c8920a527/toolkit/modules/addons/WebNavigation.jsm#L18

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

1
répondu Noitidart 2015-10-15 01:06:57

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});
0
répondu nathancahill 2015-08-27 21:34:05

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

0
répondu Victor Queiroz 2017-11-09 22:09:10

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);
})();
-1
répondu Flimm 2017-05-23 12:18:17

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()
-2
répondu user2360102 2017-02-10 10:42:02