Как получать уведомления об изменениях истории через history.pushState? - PullRequest
133 голосов
/ 31 декабря 2010

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

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

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

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

Обновление:

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

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

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

Ответы [ 9 ]

161 голосов
/ 03 января 2011

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

3 голосов
/ 04 сентября 2014

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

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

Это почти то же самое, что и галамбалаз .

Хотя обычно это излишне. И это может не работать во всех браузерах. (Я забочусь только о своей версии моего браузера.)

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

3 голосов
/ 03 января 2011

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

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

Из документов:

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

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

1 голос
/ 15 октября 2015

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

Я уверен, что 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

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

0 голосов
/ 16 января 2019

Наконец-то нашли «правильный» способ сделать это! Требуется добавить привилегию к вашему расширению и использовать фоновую страницу (не только скрипт содержимого), но это работает.

Требуемое событие - browser.webNavigation.onHistoryStateUpdated, которое вызывается, когда страница использует API-интерфейс history для изменения URL-адреса. Он срабатывает только для сайтов, на которые у вас есть разрешение, и вы также можете использовать URL-фильтр для дальнейшего сокращения спама, если это необходимо. Требуется разрешение webNavigation (и, конечно, разрешение хоста для соответствующего домена (доменов)).

Обратный вызов события получает идентификатор вкладки, URL, по которому осуществляется переход, и другие подобные подробности. Если вам нужно выполнить действие в сценарии содержимого на этой странице при возникновении события, либо внедрите соответствующий сценарий непосредственно с фоновой страницы, либо попросите сценарий содержимого открыть port для фоновой страницы при загрузке, имейте Фоновая страница сохраняет этот порт в коллекции, индексированной по идентификатору вкладки, и отправляет сообщение через соответствующий порт (от фонового сценария к сценарию содержимого) при возникновении события.

0 голосов
/ 04 ноября 2017

Хорошо, я вижу много примеров замены свойства 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

0 голосов
/ 10 февраля 2017

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

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

нам нужно вызвать history.back () для триггера WindowEventHandlers.onpopstate

Так вот:

history.pushState(...)

сделать:

history.pushState(...)
history.pushState(...)
history.back()
0 голосов
/ 28 августа 2015

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

Отказано в доступе к свойству history.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});
0 голосов
/ 03 июня 2015

ответ галамбалаза патчи обезьяны 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);
})();
...