本記事はパロニム Advent Calendar 2021の13日目の記事です。
概要
webStorage (localStorage, sessionStorage) を使うページが、シークレットウィンドウ(プライベートウィンドウ)内のiframeで読み込まれた時に、以下のような例外が出てしまって上手く動作しない場合があります。
Chromeの場合
SecurityError Failed to read the 'sessionStorage' property from 'Window': Access is denied for this document.
Safariの場合
SecurityError The operation is insecure.
Vue + vuex + vuex-persistedstate で作っていたページでこの問題が起きていたので対応を考えます。
原因
いきなり結論ですが、ブラウザのプライバシー設定が関係しており、サードパーティのCookieをブロックする設定になっているときに発生します。
Chrome
設定 → プライバシーとセキュリティ → Cookieと他のサイトデータ
「サードパーティーのCookieをブロックする」 or 「シークレットモードでサードパーティのCookieをブロックする」 or 「すべてのCookieをブロックする」
Safari
設定 → プライバシーとセキュリティ
「すべてのCookieをブロック」
この設定が有効になっているとき、iframe内で window.localStorage
を参照しただけで例外が出てしまいます。
( sessionStorage
でも同様)
特にChromeでは「シークレットモードでサードパーティのCookieをブロックする」がデフォルト設定になっているため、
シークレットウィンドウ(プライベートウィンドウ)のiframe内で発生するパターンが大半です。
vuex-persistedstate では永続化のためのストレージとしてデフォルトで localStorage が内部で利用されるため、そのままだと動作しなくなってしまいます。
永続化は無理だとしてもせめて例外によって動作が止まるのはなんとかしたいところです。
対応案
window.localStorage
を参照出来るかチェックし、出来ない場合は localStorage を利用しないようにします。
初期化時オプションの storage
で利用ストレージを指定出来るのでここでlocalStorageが利用出来ない場合はMapを使ったダミーに差し替えます。
もちろん永続化は出来ないものの、止まることは無くなります。
import { createStore } from 'vuex'
import createPersistedState from 'vuex-persistedstate'
export default createStore({
//...,
plugins: [createPersistedState({
storage: (() => {
try {
if (window.localStorage) {
return window.localStorage
}
} catch (e) {
console.log("localStorage is unavailable.")
}
const map = new Map();
return {
getItem: function (key) {
const value = map.get(String(key))
return typeof value !== 'undefined' ? value : null
},
setItem: function (key, value) {
map.set(String(key), String(value))
},
removeItem: function (key) {
map.delete(String(key))
},
}
})()
})]
})
ただ、localStorageが使える場合はシークレットウィンドウでもウィンドウを閉じるまでは書き込んだ内容が保持されるのに対し、このダミーはページ移動(リロード含む)で消えてしまうので全く同じとはならないので注意です。
要件上厳しいのであれば一番最初に明示的にユーザーに利用不可と伝えてエラーとしてしまうのも検討しましょう。
ここまで書いておいてなんですが、pluginsにcreatePersistedState()を渡さないようにするでも良いですね。
最後に
iframeでなければシークレットウィンドウでも問題が出なかったので気付くのが割と遅くなってしまいました、反省。