Cómo recibir notificaciones sobre cambios en el historial a través del historial.¿pushState?


Así que ahora que HTML5 introduce history.pushState para cambiar el historial del navegador, los sitios web comienzan a usar esto en combinación con Ajax en lugar de cambiar el identificador de fragmento de la URL.

Tristemente eso significa que esas llamadas ya no pueden ser detectadas por onhashchange.

Mi pregunta es: ¿ Hay una manera confiable (hack? ;)) para detectar cuando un sitio web utiliza history.pushState? La especificación no indica nada sobre los eventos que se plantean (al menos no pude encontrar nada).
Traté de crear una fachada y reemplazé window.history con mi propio objeto JavaScript, pero no tuvo ningún efecto en absoluto.

Explicación adicional: Estoy desarrollando un complemento para Firefox que necesita detectar estos cambios y actuar en consecuencia.
Sé que hubo una pregunta similar hace unos días que preguntaba si escuchar algunos eventos DOM sería eficiente, pero preferiría no confiar en eso porque estos eventos se pueden generar para muchos diferentes motivo.

Actualización:

Aquí hay un jsfiddle (use Firefox 4 o Chrome 8) que muestra que onpopstate no se activa cuando se llama pushState (¿o estoy haciendo algo mal? Siéntase libre de mejorarlo!).

Actualizar 2:

Otro problema (lateral) es que window.location no se actualiza cuando se usa pushState (pero leí sobre esto ya aquí, así que creo).

Author: pnuts, 2010-12-31

8 answers

5.5.9.1 Definiciones de eventos

El evento popstate se activa en ciertos casos cuando se navega a una entrada del historial de sesiones.

De acuerdo con esto, no hay razón para que popstate se dispare cuando se usa pushState. Pero un evento como pushstate sería útil. Debido a que history es un objeto host, debe tener cuidado con él, pero Firefox parece ser bueno en este caso. Este código funciona muy 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);

Tu jsfiddle se convierte en:

window.onpopstate = history.onpushstate = function(e) { ... }

Puede patch-mono window.history.replaceState de la misma manera.

Nota: por supuesto, puede agregar onpushstate simplemente al objeto global, e incluso puede hacer que maneje más eventos a través de add/removeListener

 142
Author: galambalazs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-19 23:34:51

¿Podrías enlazar al evento window.onpopstate?

Https://developer.mozilla.org/en/DOM%3awindow.onpopstate

De los documentos:

Un controlador de eventos para el popstate evento en la ventana.

Se envía un evento popstate a la ventana cada vez que el historial activo cambios de entrada. Si la entrada del historial ser activado fue creado por una llamada por la historia.pushState () o fue afectado por una llamada a la historia.replaceState(), el evento popstate propiedad del Estado contiene una copia de la entrada del historial objeto de estado.

 3
Author: stef,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-01-02 23:18:10

Solía usar esto:

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 casi lo mismo que galambalazs hizo.

Sin embargo, por lo general es excesivo. Y puede que no funcione en todos los navegadores. (Solo me importa mi versión de mi navegador.)

(Y deja un var _wr, por lo que es posible que desee envolverlo o algo. Eso no me importaba.)

 1
Author: Rudie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:34:39

Creo que este tema necesita una solución más moderna.

Estoy seguro de que nsIWebProgressListener estaba por ahí en ese entonces Me sorprende que nadie lo mencionara.

De un framescript (para compatabilidad de e10s):

let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebProgress);
webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_WINDOW | Ci.nsIWebProgress.NOTIFY_LOCATION);

Luego escuchando en el onLoacationChange

onLocationChange: function onLocationChange(webProgress, request, locationURI, flags) {
       if (flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT

Eso aparentemente atrapará a todos los pushState. Pero hay un comentario que advierte que "TAMBIÉN se activa para pushState". Así que tenemos que hacer un poco más de filtrado aquí para asegurarse de que es sólo pushstate cosas.

Basado en: https://github.com/jgraham/gecko/blob/55d8d9aa7311386ee2dabfccb481684c8920a527/toolkit/modules/addons/WebNavigation.jsm#L18

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

 1
Author: Noitidart,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-10-15 01:06:57

Ya que estás preguntando por un complemento de Firefox, aquí está el código que tengo que trabajar. Usar unsafeWindow ya no se recomienda , y se produce un error cuando se llama a pushState desde un script cliente después de ser modificado:

Permiso denegado para acceder al historial de propiedades.pushState

En su lugar, hay una API llamada exportFunction que permite que la función se inyecte en window.history de la siguiente manera:

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
Author: nathancahill,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-27 21:34:05

Bueno, veo muchos ejemplos de reemplazar la propiedad pushState de history pero no estoy seguro de que sea una buena idea, preferiría crear un evento de servicio basado en una API similar al historial de esa manera puede controlar no solo el estado de inserción sino también el estado de reemplazo y abrir puertas para muchas otras implementaciones que no dependen de la API de historial global. Por favor revise el siguiente ejemplo:

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 necesita la definición EventEmitter, el código anterior se basa en el emisor de eventos NodeJS: https://github.com/nodejs/node/blob/36732084db9d0ff59b6ce31e839450cd91a156be/lib/events.js. utils.inherits la implementación se puede encontrar aquí: https://github.com/nodejs/node/blob/36732084db9d0ff59b6ce31e839450cd91a156be/lib/util.js#L970

 0
Author: Victor Queiroz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-09 22:09:10

La respuesta de Galambalazs parches de mono window.history.pushState y window.history.replaceState, pero por alguna razón dejó de funcionar para mí. Aquí hay una alternativa que no es tan agradable porque utiliza encuestas:

(function() {
    var previousState = window.history.state;
    setInterval(function() {
        if (previousState !== window.history.state) {
            previousState = window.history.state;
            myCallback();
        }
    }, 100);
})();
 -1
Author: Flimm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:18:17

Como estados estándar:

Tenga en cuenta que solo llamar a la historia.pushState () o history.replaceState () no activará un evento popstate. El evento popstate solo se activa haciendo una acción del navegador, como hacer clic en el botón atrás (o llamar al historial).() en JavaScript)

Necesitamos llamar al historial.volver() a trigeer WindowEventHandlers.onpopstate

Así insted de:

history.pushState(...)

Do:

history.pushState(...)
history.pushState(...)
history.back()
 -2
Author: user2360102,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-10 10:42:02