Как получить уведомление об изменениях истории через history.pushState?

Итак, что HTML5 вводит history.pushState чтобы изменить историю браузеров, веб-сайты начинают использовать это в сочетании с Ajax вместо изменения идентификатора фрагмента URL.

к сожалению, это означает, что эти вызовы больше не могут быть обнаружены onhashchange.

мой вопрос: есть ли надежный способ (hack? ;)) чтобы определить, когда веб-сайт использует history.pushState? В спецификации ничего не говорится о событиях, которые возникают (в по крайней мере, я ничего не нашел).
Я попытался создать фасад и заменил window.history С моим собственным объектом JavaScript, но он не имел никакого эффекта вообще.

дальнейших объяснений: я разрабатываю дополнение Firefox, которое должно обнаруживать эти изменения и действовать соответственно.
Я знаю, что был аналогичный вопрос несколько дней назад, который спросил, слушает ли некоторые событий DOM было бы эффективно, но я бы предпочел не полагаться на это, потому что эти события могут быть сгенерированы по разным причинам.

обновление:

вот jsfiddle (используйте Firefox 4 или Chrome 8), который показывает, что onpopstate не срабатывает, когда pushState называется (или я делаю что-то неправильно? Не стесняйтесь улучшать его!).

обновление 2:

другая (сторона) проблема в том, что window.location не обновляется при использовании pushState (но я читал об этом уже здесь, поэтому я думать.)

8 ответов


5.5.9.1 определения событий

на popstate событие запускается в некоторых случаях при переходе к сеансу записи.

в соответствии с этим, нет причин для popstate быть уволен, когда вы используете pushState. Но такое событие, как pushstate пригодится. Потому что history - это объект, вы должны быть осторожны с ним, но Firefox, кажется, хорошо в этом случае. Этот код работает только отлично:

(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);

ваш jsfiddle становится:

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

вы можете обезьяна-патч window.history.replaceState таким же образом.

Примечание: конечно, вы можете добавить onpushstate просто к глобальному объекту, и вы даже можете сделать его обрабатывать больше событий через add/removeListener


вы можете привязаться к window.onpopstate событие?

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

документы:

обработчик событий для popstate событие на окне.

событие popstate отправляется в окно каждый раз, когда активная история запись меняется. Если запись истории активация была создана вызовом за историю.pushState () или был затронут звонком история.replaceState(), свойство состояния события popstate содержит копию записи истории государственный объект.


я использовал этот:

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!');
});

это почти то же самое, что galambalazs сделал.

это обычно перебор, хотя. И это может работать не во всех браузерах. (Меня волнует только моя версия браузера.)

(и он оставляет var _wr, поэтому вы можете завернуть его или что-то еще. Меня это не волновало.)


Я думаю, что эта тема нуждается в более современное решение.

уверен nsIWebProgressListener тогда еще я удивлен, что никто не упомянул об этом.

из фреймскрипта (для совместимости с e10s):

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

затем прослушивание в onLoacationChange

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

это, по-видимому, поймает все pushState. Но есть предупреждение комментария, что он "также запускает для pushState". Поэтому нам нужно сделать еще одну фильтрацию здесь, чтобы убедиться, что это просто pushstate материал.

на основе: https://github.com/jgraham/gecko/blob/55d8d9aa7311386ee2dabfccb481684c8920a527/toolkit/modules/addons/WebNavigation.jsm#L18

и: ресурс: / / gre / модули / WebNavigationContent.js


поскольку вы спрашиваете о аддоне Firefox, вот код, который я должен работать. Используя unsafeWindow is не рекомендуется и ошибки, когда pushState вызывается из клиентского скрипта после изменения:

разрешение отказано в доступе к истории свойств.pushState

вместо этого есть API под названием exportFunction что позволяет функции быть впрыснутым в window.history такой:

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});

Ну, я вижу много примеров замены pushState собственность history но я не уверен, что это хорошая идея, я бы предпочел создать событие службы на основе аналогичного API для истории, таким образом, вы можете контролировать не только состояние push, но и заменять состояние, а также открывать двери для многих других реализаций, не полагаясь на API глобальной истории. Пожалуйста, проверьте следующий пример:

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];
});

Если вам нужны EventEmitter определение, код выше основан на событии NodeJS излучатель: https://github.com/nodejs/node/blob/36732084db9d0ff59b6ce31e839450cd91a156be/lib/events.js. utils.inherits реализацию можно найти здесь: https://github.com/nodejs/node/blob/36732084db9d0ff59b6ce31e839450cd91a156be/lib/util.js#L970


galambalazs это!--5--> обезьяна патчи window.history.pushState и window.history.replaceState, но по какой-то причине он перестал работать для меня. Вот альтернатива, которая не так хороша, потому что она использует опрос:

(function() {
    var previousState = window.history.state;
    setInterval(function() {
        if (previousState !== window.history.state) {
            previousState = window.history.state;
            myCallback();
        }
    }, 100);
})();

как стандартные состояния:

обратите внимание, что просто вызов истории.pushState() или истории.replaceState () не вызовет событие popstate. Событие popstate запускается только при выполнении действия браузера, такого как нажатие кнопки "Назад" (или история вызовов).back () в JavaScript)

нужно звонки.back () в trigeer WindowEventHandlers.onpopstate

Так insted из:

history.pushState(...)

do:

history.pushState(...)
history.pushState(...)
history.back()