web-dev-qa-db-ja.com

history.pushStateを介して履歴の変更について通知を受けるにはどうすればよいですか?

HTML5はブラウザの履歴を変更するために history.pushState を導入したので、WebサイトはURLのフラグメント識別子を変更する代わりにこれをAjaxと組み合わせて使用​​し始めます。

残念なことに、これらの呼び出しはonhashchangeで検出できなくなります。

私の質問は次のとおりです:Webサイトがhistory.pushStateを使用していることを検出する信頼できる方法(ハック?;))はありますか?仕様には、発生したイベントについては何も記載されていません(少なくとも、私は何も見つかりませんでした)。
ファサードを作成し、window.historyを独自のJavaScriptオブジェクトに置き換えましたが、まったく効果がありませんでした。

詳細説明:これらの変更を検出し、それに応じて行動する必要があるFirefoxアドオンを開発しています。
数日前に DOMイベント を聞くことが効率的かどうかを尋ねる同様の質問があったことは知っていますが、これらのイベントは多くの異なる理由。

更新:

こちらはjsfiddleです (Firefox 4またはChrome 8を使用)onpopstateが呼び出されたときにpushStateがトリガーされないことを示します(または私が何かをしていますか間違っていますか?自由に改善してください!)。

更新2:

もう1つの(側面)問題は、pushStateを使用するときにwindow.locationが更新されないことです(ただし、これについてはSOで既に読んでいます)。

139
Felix Kling

5.5.9.1イベント定義

popstateイベントは、特定の場合にセッション履歴エントリに移動するときに発生します。

これによると、pushStateを使用するときにpopstateが起動される理由はありません。ただし、pushstateなどのイベントが便利です。 historyはHostオブジェクトなので、注意する必要がありますが、この場合は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);

あなたのjsfiddlebecomes

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

同じ方法でwindow.history.replaceStateをモンキーパッチできます。

注:もちろん、単純にグローバルオブジェクトにonpushstateを追加できます。また、add/removeListenerを介してより多くのイベントを処理することもできます。

163
gblazex

window.onpopstateイベントにバインドできますか?

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

ドキュメントから:

ウィンドウのpopstateイベントのイベントハンドラー。

アクティブな履歴エントリが変更されるたびに、popstateイベントがウィンドウに送出されます。アクティブ化される履歴エントリがhistory.pushState()の呼び出しによって作成されたか、history.replaceState()の呼び出しの影響を受けた場合、popstateイベントのstateプロパティには、履歴エントリの状態オブジェクトのコピーが含まれます。

3
stef

私はこれを使用していました:

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を残すので、あなたはそれか何かをラップしたいかもしれません。私はそれを気にしませんでした。)

3
Rudie

このトピックには、より現代的なソリューションが必要だと思います。

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をキャッチします。しかし、「alsoがpushStateをトリガーする」という警告のコメントがあります。そのため、ここでさらにフィルタリングを行い、pushstateのものにする必要があります。

ベース: https://github.com/jgraham/gecko/blob/55d8d9aa7311386ee2dabfccb481684c8920a527/toolkit/modules/addons/WebNavigation.jsm#L18

そして:resource://gre/modules/WebNavigationContent.js

1
Noitidart

さて、pushStatehistoryプロパティを置き換える多くの例を見ることができますが、それが良いアイデアかどうかはわかりません。プッシュ状態だけでなく、状態を置き換えることも制御できます。これにより、グローバル履歴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.jsutils.inherits実装はここにあります: https://github.com/nodejs/node/blob/36732084db9d0ff59b6ce31e839450cd91a156be/lib/util.js#L97

0
Victor Queiroz

これを行うための「正しい」方法がついに見つかりました!拡張機能に特権を追加し、バックグラウンドページ(コンテンツスクリプトだけでなく)を使用する必要がありますが、機能します。

必要なイベントは browser.webNavigation.onHistoryStateUpdated です。これは、ページがhistory AP​​Iを使用してURLを変更すると起動されます。アクセス許可があるサイトに対してのみ起動します。必要に応じて、URLフィルターを使用してスパムをさらに削減することもできます。 webNavigationパーミッション(および関連するドメインのホストパーミッション)が必要です。

イベントコールバックは、タブID、「ナビゲート」されるURL、およびその他の詳細を取得します。イベントが発生したときにそのページのコンテンツスクリプトでアクションを実行する必要がある場合は、関連するスクリプトをバックグラウンドページから直接挿入するか、ロード時にコンテンツスクリプトにportをバックグラウンドページに開かせます。バックグラウンドページでそのポートをタブIDでインデックス付けされたコレクションに保存し、イベントが発生したときに関連するポート(バックグラウンドスクリプトからコンテンツスクリプトへ)でメッセージを送信します。

0
CBHacking

Firefoxのアドオンについて質問しているので、ここで作業するコードを示します。 unsafeWindowの使用は 推奨されなくなりました であり、修正後にpushStateがクライアントスクリプトから呼び出されるとエラーが発生します。

プロパティhistory.pushStateへのアクセス許可が拒否されました

代わりに、 exportFunction というAPIがあり、次のように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
nathancahill

galambalazsの答え モンキーパッチ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);
})();
0
Flimm