`history.replaceState()` にはブラウザによって呼び出し回数制限がある
`history.replaceState()` は、ブラウザの履歴を変更するための API です。使用例としてユーザーのアクションによる UI の状態の変更に合わせて、URL のクエリパラメータを変更することが挙げられます。この API はブラウザにより呼び出し回数に制限が設けられており、使い方を誤ると予期せぬ挙動が発生するおそれがあります。
history.replaceState() は、ブラウザの履歴を変更するための API です。使用例としてユーザーのアクションによる UI の状態の変更に合わせて、URL のクエリパラメータを変更することが挙げられます。例えば、検索フォームに入力したキーワードをクエリパラメータに設定することで、検索結果をブックマークしたり、URL を共有したりできるようになります。
history.replaceState()
にはブラウザによって制限がある
history.replaceState()
は、ブラウザによって制限があることをご存知でしょうか?一定の時間内に history.replaceState()
呼び出すことができる回数に制限があるのです。試しに以下のコードを実行してみてください。
<div id="hoge"></div>
<script>
window.addEventListener("load", function () {
var query = location.search.replace("?q=", "");
document.querySelector("#hoge").textContent = query;
});
for (var i = 0; i <= 10000; i++) {
history.replaceState(null, null, `?q=${i}`);
}
</script>
Google Chrome で上記のコードを実行すると、以下のような警告が発生します。
Throttling navigation to prevent the browser from hanging. See https://crbug.com/1038223. Command line switch --disable-ipc-flooding-protection can be used to bypass the protection
ブラウザのハングアップを防ぐために、ナビゲーションをスロットリングしているという警告です。実際にクエリパラメータにはループの最後の値である 10000
ではなく、199
であることが確認できます。
現実のアプリケーションにおいても、ブラウザによりスロットリングが実行されることにより、意図しない挙動を起こす可能性があります。例えば、検索フォームに高速にキーワードが入力された場合、フォームに入力された検索キーワードとクエリパラメータが一致しないおそれがあります。
ブラウザによる制限の違い
history.replaceState()
の呼び出しの制限はブラウザによって異なります。正確な値ではないものの、Google Chrome、Firefox、Edge ではおおむね 50ms ごとにスロットリングして処理を行えば、ブラウザによる制限を回避できるようです。ただし、Safari ではより厳しい制限があるようです。30s に 100 回の呼び出しに制限されています。
SecurityError: Attempt to use history.replaceState() more than 100 times per 30 seconds
解決策
history.replaceState()
の呼び出しの制限を回避するためには lodash.throttle
などのスロットリング関数を利用することが考えられます。
<script type="module">
import { throttle } from "https://cdn.skypack.dev/lodash";
const throttled = throttle((url) => {
history.pushState(null, null, url);
}, 100); // この値は適当
window.addEventListener("load", function () {
setTimeout(() => {
var query = location.search.replace("?q=", "");
console.log(query);
document.querySelector("#hoge").textContent = query;
}, 100); // history.replaceState() の呼び出しが遅れるので、ここでも setTimeout でちょっと待つ必要がある
});
const loopCount = 10000;
for (var i = 0; i <= loopCount; i++) {
throttled(`?q=${i}`);
}
</script>
まとめ
history.replaceState()
は、ブラウザの履歴を変更するための API- ブラウザによって
history.replaceState()
の呼び出し回数に制限がある lodash.throttle
などのスロットリング関数を利用することで、ブラウザによる制限を回避できる