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で既に読んでいます)。
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
を介してより多くのイベントを処理することもできます。
window.onpopstate
イベントにバインドできますか?
https://developer.mozilla.org/en/DOM%3awindow.onpopstate
ドキュメントから:
ウィンドウのpopstateイベントのイベントハンドラー。
アクティブな履歴エントリが変更されるたびに、popstateイベントがウィンドウに送出されます。アクティブ化される履歴エントリがhistory.pushState()の呼び出しによって作成されたか、history.replaceState()の呼び出しの影響を受けた場合、popstateイベントのstateプロパティには、履歴エントリの状態オブジェクトのコピーが含まれます。
私はこれを使用していました:
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をキャッチします。しかし、「alsoがpushStateをトリガーする」という警告のコメントがあります。そのため、ここでさらにフィルタリングを行い、pushstateのものにする必要があります。
そして:resource://gre/modules/WebNavigationContent.js
さて、pushState
のhistory
プロパティを置き換える多くの例を見ることができますが、それが良いアイデアかどうかはわかりません。プッシュ状態だけでなく、状態を置き換えることも制御できます。これにより、グローバル履歴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#L97
これを行うための「正しい」方法がついに見つかりました!拡張機能に特権を追加し、バックグラウンドページ(コンテンツスクリプトだけでなく)を使用する必要がありますが、機能します。
必要なイベントは browser.webNavigation.onHistoryStateUpdated
です。これは、ページがhistory
APIを使用してURLを変更すると起動されます。アクセス許可があるサイトに対してのみ起動します。必要に応じて、URLフィルターを使用してスパムをさらに削減することもできます。 webNavigation
パーミッション(および関連するドメインのホストパーミッション)が必要です。
イベントコールバックは、タブID、「ナビゲート」されるURL、およびその他の詳細を取得します。イベントが発生したときにそのページのコンテンツスクリプトでアクションを実行する必要がある場合は、関連するスクリプトをバックグラウンドページから直接挿入するか、ロード時にコンテンツスクリプトにport
をバックグラウンドページに開かせます。バックグラウンドページでそのポートをタブIDでインデックス付けされたコレクションに保存し、イベントが発生したときに関連するポート(バックグラウンドスクリプトからコンテンツスクリプトへ)でメッセージを送信します。
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});
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);
})();