[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"article-storage-event-cross-tab-sync":44,"search-suggestions":242,"related-storage-event-cross-tab-sync":282,"code:js:true:cjtzc3":415,"code:js:true:pix2jh":416,"comments-01a0f615-e1c2-729c-b09a-6e35ad0eba80":417},[4,20,32],{"id":5,"slug":6,"name":7,"tagline":8,"description":9,"accentFrom":10,"accentTo":11,"icon":12,"defaultLocale":13,"locales":14,"features":16,"position":19},"019fe637-3d33-714b-b57f-23e163ffca0c","dev","Web Development","Read it. Run it. Prove it.","A post a day on modern web development — most with an editable playground and a quiz that explains every answer. Free, no account needed.","violet-500","cyan-400","◇","en",[13,15],"fa",{"courses":17,"paths":17,"articles":17,"exams":18,"flashcards":18,"packages":17,"community":17,"certificates":17,"teams":17,"commerce":17},true,false,0,{"id":21,"slug":22,"name":23,"tagline":24,"description":25,"accentFrom":26,"accentTo":10,"icon":27,"defaultLocale":13,"locales":28,"features":30,"position":31},"019fe637-3dc2-754c-8657-0f175bfee7c6","lang","Languages","Learn a language the way you learn a codebase.","Grammar explained the way good documentation explains an API — one idea at a time, each with a quiz.","amber-400","⌘",[13,15,29],"es",{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":17,"packages":18,"community":17,"certificates":17,"teams":18,"commerce":18},2,{"id":33,"slug":34,"name":35,"tagline":36,"description":37,"accentFrom":38,"accentTo":39,"icon":40,"defaultLocale":13,"locales":41,"features":42,"position":43},"7b3c16f2-931d-410e-802e-e1fa4edab7de","soft","Soft Skills","The half of the job nobody wrote documentation for.","Weekly, on the parts of working life that decide more than your code does — first weeks, meetings, interviews, promotions, and the people around you. Written from what actually happens, and recorded as a podcast you can listen to on the walk.","emerald-400","teal-300","◉",[13],{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":18,"packages":18,"community":17,"certificates":18,"teams":18,"commerce":18},3,{"id":45,"slug":46,"title":47,"subtitle":48,"excerpt":49,"coverUrl":50,"locale":13,"readingMinutes":51,"publishedAt":52,"viewCount":53,"likeCount":19,"commentCount":19,"author":54,"vertical":59,"topic":60,"tags":63,"_count":75,"playground":77,"body":79,"bodyMd":225,"seo":226,"translationGroupId":229,"series":48,"podcastUrl":48,"verticalId":5,"thread":230,"assessments":232,"translations":238,"quiz":240},"01a0f615-e1c2-729c-b09a-6e35ad0eba80","storage-event-cross-tab-sync","I Built Cross-Tab Logout. The storage Event Skipped One Tab.",null,"A cross-tab 'log out everywhere' feature built on the storage event worked in every tab except the one the user actually clicked in. The storage event never fires in the tab that made the change — and that's spec, not a bug.","\u002Fmedia\u002Fcovers\u002Fstorage-event-cross-tab-sync.png",6,"2026-10-06T06:11:02.312Z",38,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},"javascript","JavaScript",[64,66,69,72],{"slug":61,"name":65,"color":48},"Javascript",{"slug":67,"name":68,"color":48},"webdev","Webdev",{"slug":70,"name":71,"color":48},"frontend","Frontend",{"slug":73,"name":74,"color":48},"browser","Browser",{"assessments":76},1,{"slug":46,"title":78},"storage event — cross-tab playground",{"blocks":80,"version":76},[81,85,88,91,96,99,105,108,112,115,118,124,127,130,133,137,140,144,147,150,153,156,159,162,165,168,171,174,177,180,183,186,189,192,195,198,201,207,210,213,216,219],{"id":82,"html":83,"type":84},"b1","\u003Cp>Three tabs open, same account, same session. Click &quot;log out&quot; in the middle one, and the plan is simple: every tab reacts, every tab redirects to the login screen, nobody&#39;s left holding a stale session.\u003C\u002Fp>","paragraph",{"id":86,"html":87,"type":84},"b2","\u003Cp>I wired it the way you&#39;d expect — \u003Ccode>localStorage.setItem(&#39;session&#39;, JSON.stringify({ loggedIn: false }))\u003C\u002Fcode> on logout, a \u003Ccode>window.addEventListener(&#39;storage&#39;, ...)\u003C\u002Fcode> listener that reads the new value and updates the UI. Opened two tabs to test it. Clicked logout in the first.\u003C\u002Fp>",{"id":89,"html":90,"type":84},"b3","\u003Cp>The second tab redirected instantly. The first tab — the one I&#39;d just clicked in — sat there, fully rendered, still logged in, still showing the account menu, like nothing had happened.\u003C\u002Fp>",{"id":92,"html":93,"text":94,"type":95,"level":31},"b4","The obvious read: &quot;listen for storage changes&quot;","The obvious read: \"listen for storage changes\"","heading",{"id":97,"html":98,"type":84},"b5","\u003Cp>Before \u003Ccode>BroadcastChannel\u003C\u002Fcode> existed (and still, in plenty of production code, when you need something that works back to IE11 and doesn&#39;t need a build step), the \u003Ccode>storage\u003C\u002Fcode> event was \u003Cem>the\u003C\u002Fem> way to sync tabs. The pitch is simple: any tab writes to \u003Ccode>localStorage\u003C\u002Fcode>, every other tab watching that origin gets notified.\u003C\u002Fp>",{"id":100,"code":101,"type":102,"language":103,"highlight":104},"b6","function applyLogout(loggedIn) {\n  if (!loggedIn) {\n    renderLoggedOut();\n  }\n}\n\nwindow.addEventListener(\"storage\", (e) => {\n  if (e.key !== \"session\") return;\n  const { loggedIn } = JSON.parse(e.newValue);\n  applyLogout(loggedIn);\n});\n\n\u002F\u002F somewhere in the logout button's click handler:\nlocalStorage.setItem(\"session\", JSON.stringify({ loggedIn: false }));","code","js",[],{"id":106,"html":107,"type":84},"b7","\u003Cp>Reasonable code. It even looks correct in a two-tab test, if you only check the \u003Cem>other\u003C\u002Fem> tab. I didn&#39;t — I assumed the tab making the change would see its own write reflected the same way, because that&#39;s how almost every other kind of state-change notification behaves: you change something, your own UI picks it up.\u003C\u002Fp>",{"id":109,"html":110,"text":111,"type":95,"level":31},"b8","What&#39;s actually happening: the event skips its origin","What's actually happening: the event skips its origin",{"id":113,"html":114,"type":84},"b9","\u003Cp>It&#39;s in the first sentence of MDN&#39;s page for the event, which I&#39;d clearly skimmed instead of read: the \u003Ccode>storage\u003C\u002Fcode> event fires on \u003Ccode>Window\u003C\u002Fcode> objects \u003Cstrong>other than\u003C\u002Fstrong> the one that made the change. The tab that calls \u003Ccode>setItem\u003C\u002Fcode> doesn&#39;t get a \u003Ccode>storage\u003C\u002Fcode> event for its own write — not sometimes, not as a quirk in one browser. Every engine implements it this way, because the event&#39;s whole job is telling \u003Cem>other\u003C\u002Fem> documents &quot;something changed somewhere else.&quot; The document where it changed already knows; dispatching the event back to itself would just be an echo.\u003C\u002Fp>",{"id":116,"html":117,"type":84},"b10","\u003Cp>Two more edges worth knowing while you&#39;re down here, because they&#39;re exactly the kind of thing that passes a two-browser-tab smoke test and then breaks in a slightly different shape in production:\u003C\u002Fp>",{"id":119,"type":120,"items":121,"ordered":18},"b11","list",[122,123],"\u003Cstrong>No real change, no event.\u003C\u002Fstrong> \u003Ccode>setItem(&#39;theme&#39;, &#39;dark&#39;)\u003C\u002Fcode> when \u003Ccode>theme\u003C\u002Fcode> was already \u003Ccode>&#39;dark&#39;\u003C\u002Fcode> doesn&#39;t fire anything, anywhere — the spec compares old and new values first. If your UI update logic lives entirely inside the storage listener, writing the &quot;same&quot; value on purpose (to force a refresh, say) silently does nothing.","\u003Cstrong>\u003Ccode>clear()\u003C\u002Fcode> reports \u003Ccode>key: null\u003C\u002Fcode>.\u003C\u002Fstrong> Wiping the whole storage area doesn&#39;t name a key — \u003Ccode>event.key\u003C\u002Fcode>, \u003Ccode>oldValue\u003C\u002Fcode>, and \u003Ccode>newValue\u003C\u002Fcode> all come back \u003Ccode>null\u003C\u002Fcode>, which is the spec&#39;s way of saying &quot;everything,&quot; not &quot;nothing.&quot;",{"id":125,"html":126,"type":84},"b12","\u003Cp>Neither one bit me this time. The missing self-notification did, because the entire feature&#39;s correctness depended on exactly one tab — the one where the user clicked — getting word of its own action, and that&#39;s precisely the tab the event is defined to skip.\u003C\u002Fp>",{"id":128,"html":129,"text":129,"type":95,"level":31},"b13","The fix is two call sites, not a different API",{"id":131,"html":132,"type":84},"b14","\u003Cp>The temptation is to blame the event and reach for something else. The actual fix is smaller: stop treating &quot;write to storage&quot; and &quot;update this tab&#39;s UI&quot; as the same step just because they usually travel together.\u003C\u002Fp>",{"id":134,"code":135,"type":102,"language":103,"highlight":136},"b15","function applyLogout(loggedIn) {\n  if (!loggedIn) {\n    renderLoggedOut();\n  }\n}\n\nwindow.addEventListener(\"storage\", (e) => {\n  if (e.key !== \"session\") return;\n  const { loggedIn } = JSON.parse(e.newValue);\n  applyLogout(loggedIn); \u002F\u002F every OTHER tab arrives here\n});\n\nfunction logout() {\n  localStorage.setItem(\"session\", JSON.stringify({ loggedIn: false }));\n  applyLogout(false); \u002F\u002F THIS tab has to call itself — no event is coming\n}",[],{"id":138,"html":139,"type":84},"b16","\u003Cp>One function, two call sites: the listener covers every tab that didn&#39;t make the change, and the action itself covers the one that did. It&#39;s an easy rule to forget specifically because it&#39;s invisible in testing unless you remember to check the tab you clicked in — which, if you&#39;re the one testing your own feature, is also the tab you&#39;re already looking at and already know the &quot;right&quot; answer for.\u003C\u002Fp>",{"id":141,"html":142,"text":143,"type":95,"level":31},"b17","The one thing this old API still does that a newer one can&#39;t","The one thing this old API still does that a newer one can't",{"id":145,"html":146,"type":84},"b18","\u003Cp>It&#39;s tempting to read all of this as &quot;just use \u003Ccode>BroadcastChannel\u003C\u002Fcode> instead&quot; — the API that replaced \u003Ccode>storage\u003C\u002Fcode>-event tab-syncing for most new code, because it skips the JSON-stringify dance and doesn&#39;t fire on unrelated key changes. It&#39;s a genuinely better fit for most of what people reach for \u003Ccode>storage\u003C\u002Fcode> events to do.\u003C\u002Fp>",{"id":148,"html":149,"type":84},"b19","\u003Cp>But it trades away something the \u003Ccode>storage\u003C\u002Fcode> event gets for free, because \u003Ccode>localStorage\u003C\u002Fcode> isn&#39;t just a message pipe — it&#39;s a place the value actually lives. Open a \u003Cem>third\u003C\u002Fem> tab a few minutes after the logout happened in the other two, with no tabs sending anything at that moment, and it still loads logged out — because on load, it reads \u003Ccode>localStorage.getItem(&#39;session&#39;)\u003C\u002Fcode> directly, the same way it would read any other persisted value. There was no message to miss, because nothing had to be delivered. \u003Ccode>BroadcastChannel\u003C\u002Fcode> only reaches tabs that are listening at the moment a message is posted; a tab that opens later never receives what it missed, because there&#39;s nothing left to receive — the channel doesn&#39;t keep a backlog. If a new tab needs to know the \u003Cem>current\u003C\u002Fem> state rather than just future \u003Cem>changes\u003C\u002Fem> to it, something has to persist that state somewhere readable — and \u003Ccode>localStorage\u003C\u002Fcode> plus the \u003Ccode>storage\u003C\u002Fcode> event is still a reasonable way to get both the persistence and the live update in one write.\u003C\u002Fp>",{"id":151,"html":152,"type":84},"b20","\u003C!-- playground:start -->",{"id":154,"html":155,"text":155,"type":95,"level":31},"b21","🎮 Try it yourself",{"id":157,"html":158,"type":84},"b22","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fstorage-event-cross-tab-sync\u002Fplayground\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":160,"html":161,"type":84},"b23","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":163,"html":164,"type":84},"b24","\u003C!-- playground:end -->",{"id":166,"html":167,"text":167,"type":95,"level":31},"b25","Try it with two real tabs",{"id":169,"html":170,"type":84},"b26","\u003Cp>Everything above is easy to nod along to and easy to get backwards in actual code, because the bug only shows up in the tab you&#39;re not looking at — or, worse, the one you are, silently not updating while you assume it did. Open the demo below in two tabs and watch both directions at once: the tab that writes, and the tab that&#39;s told.\u003C\u002Fp>",{"id":172,"html":173,"text":173,"type":95,"level":31},"b27","The part worth remembering",{"id":175,"html":176,"type":84},"b28","\u003Cp>None of this is a \u003Ccode>storage\u003C\u002Fcode>-event bug. It&#39;s a one-line spec detail that&#39;s easy to miss because almost nothing else behaves this way — you change a variable, your own scope sees the new value; you call a setter, your own component re-renders. A browser event that deliberately excludes the one document that caused it is the exception, not the rule, and the only way to catch it before production is to actually check the tab you clicked in, not just the one next to it.\u003C\u002Fp>",{"id":178,"html":179,"type":84},"b29","\u003Cp>What&#39;s the last cross-tab or cross-window feature you shipped where you only tested the \u003Cem>other\u003C\u002Fem> window?\u003C\u002Fp>",{"id":181,"html":182,"type":84},"b30","\u003C!-- quiz:start -->",{"id":184,"html":185,"text":185,"type":95,"level":31},"b31","🧠 Test yourself",{"id":187,"html":188,"type":84},"b32","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fstorage-event-cross-tab-sync\u002Fquiz\">Take the 8-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":190,"html":191,"type":84},"b33","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":193,"html":194,"type":84},"b34","\u003C!-- quiz:end -->",{"id":196,"html":197,"type":84},"b35","\u003C!-- related:start -->",{"id":199,"html":200,"text":200,"type":95,"level":31},"b36","📚 Read next",{"id":202,"type":120,"items":203,"ordered":18},"b37",[204,205,206],"\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fbroadcast-channel-cross-tab-messaging\">Stop using the localStorage hack to sync browser tabs. BroadcastChannel does it natively.\u003C\u002Fa>","\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fpage-visibility-api-tab-aware-timers\">Your Timer Doesn&#39;t Know You Tabbed Away\u003C\u002Fa>","\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fweb-locks-api-tab-coordination\">Five tabs open, one refresh token — the race nobody noticed\u003C\u002Fa>",{"id":208,"html":209,"type":84},"b38","\u003C!-- related:end -->",{"id":211,"type":212},"b39","divider",{"id":214,"html":215,"type":84},"b40","\u003Cp>🚀 \u003Cstrong>Want more like this?\u003C\u002Fstrong> Every guide, playground, and quiz lives on \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">bestpractic.org\u003C\u002Fa>\u003C\u002Fstrong> — open it and \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">sign up free\u003C\u002Fa>\u003C\u002Fstrong> so the next one finds you.\u003C\u002Fp>",{"id":217,"html":218,"type":84},"b41","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":220,"type":120,"items":221,"ordered":18},"b42",[222,223,224],"⭐ \u003Cstrong>GitHub\u003C\u002Fstrong> — follow me and star the projects: \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fparsajiravand\">github.com\u002Fparsajiravand\u003C\u002Fa>","💬 \u003Cstrong>Discord\u003C\u002Fstrong> — join the frontend best-practices community: \u003Ca href=\"https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ\">discord.gg\u002Fd9KRhuAwQ\u003C\u002Fa>","📸 \u003Cstrong>Instagram\u003C\u002Fstrong> — frontend best practices, daily: \u003Ca href=\"https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F\">@bestpractice___\u003C\u002Fa>","Three tabs open, same account, same session. Click \"log out\" in the middle one, and the plan is simple: every tab reacts, every tab redirects to the login screen, nobody's left holding a stale session.\n\nI wired it the way you'd expect — `localStorage.setItem('session', JSON.stringify({ loggedIn: false }))` on logout, a `window.addEventListener('storage', ...)` listener that reads the new value and updates the UI. Opened two tabs to test it. Clicked logout in the first.\n\nThe second tab redirected instantly. The first tab — the one I'd just clicked in — sat there, fully rendered, still logged in, still showing the account menu, like nothing had happened.\n\n## The obvious read: \"listen for storage changes\"\n\nBefore `BroadcastChannel` existed (and still, in plenty of production code, when you need something that works back to IE11 and doesn't need a build step), the `storage` event was *the* way to sync tabs. The pitch is simple: any tab writes to `localStorage`, every other tab watching that origin gets notified.\n\n```js\nfunction applyLogout(loggedIn) {\n  if (!loggedIn) {\n    renderLoggedOut();\n  }\n}\n\nwindow.addEventListener(\"storage\", (e) => {\n  if (e.key !== \"session\") return;\n  const { loggedIn } = JSON.parse(e.newValue);\n  applyLogout(loggedIn);\n});\n\n\u002F\u002F somewhere in the logout button's click handler:\nlocalStorage.setItem(\"session\", JSON.stringify({ loggedIn: false }));\n```\n\nReasonable code. It even looks correct in a two-tab test, if you only check the *other* tab. I didn't — I assumed the tab making the change would see its own write reflected the same way, because that's how almost every other kind of state-change notification behaves: you change something, your own UI picks it up.\n\n## What's actually happening: the event skips its origin\n\nIt's in the first sentence of MDN's page for the event, which I'd clearly skimmed instead of read: the `storage` event fires on `Window` objects **other than** the one that made the change. The tab that calls `setItem` doesn't get a `storage` event for its own write — not sometimes, not as a quirk in one browser. Every engine implements it this way, because the event's whole job is telling *other* documents \"something changed somewhere else.\" The document where it changed already knows; dispatching the event back to itself would just be an echo.\n\nTwo more edges worth knowing while you're down here, because they're exactly the kind of thing that passes a two-browser-tab smoke test and then breaks in a slightly different shape in production:\n\n- **No real change, no event.** `setItem('theme', 'dark')` when `theme` was already `'dark'` doesn't fire anything, anywhere — the spec compares old and new values first. If your UI update logic lives entirely inside the storage listener, writing the \"same\" value on purpose (to force a refresh, say) silently does nothing.\n- **`clear()` reports `key: null`.** Wiping the whole storage area doesn't name a key — `event.key`, `oldValue`, and `newValue` all come back `null`, which is the spec's way of saying \"everything,\" not \"nothing.\"\n\nNeither one bit me this time. The missing self-notification did, because the entire feature's correctness depended on exactly one tab — the one where the user clicked — getting word of its own action, and that's precisely the tab the event is defined to skip.\n\n## The fix is two call sites, not a different API\n\nThe temptation is to blame the event and reach for something else. The actual fix is smaller: stop treating \"write to storage\" and \"update this tab's UI\" as the same step just because they usually travel together.\n\n```js\nfunction applyLogout(loggedIn) {\n  if (!loggedIn) {\n    renderLoggedOut();\n  }\n}\n\nwindow.addEventListener(\"storage\", (e) => {\n  if (e.key !== \"session\") return;\n  const { loggedIn } = JSON.parse(e.newValue);\n  applyLogout(loggedIn); \u002F\u002F every OTHER tab arrives here\n});\n\nfunction logout() {\n  localStorage.setItem(\"session\", JSON.stringify({ loggedIn: false }));\n  applyLogout(false); \u002F\u002F THIS tab has to call itself — no event is coming\n}\n```\n\nOne function, two call sites: the listener covers every tab that didn't make the change, and the action itself covers the one that did. It's an easy rule to forget specifically because it's invisible in testing unless you remember to check the tab you clicked in — which, if you're the one testing your own feature, is also the tab you're already looking at and already know the \"right\" answer for.\n\n## The one thing this old API still does that a newer one can't\n\nIt's tempting to read all of this as \"just use `BroadcastChannel` instead\" — the API that replaced `storage`-event tab-syncing for most new code, because it skips the JSON-stringify dance and doesn't fire on unrelated key changes. It's a genuinely better fit for most of what people reach for `storage` events to do.\n\nBut it trades away something the `storage` event gets for free, because `localStorage` isn't just a message pipe — it's a place the value actually lives. Open a *third* tab a few minutes after the logout happened in the other two, with no tabs sending anything at that moment, and it still loads logged out — because on load, it reads `localStorage.getItem('session')` directly, the same way it would read any other persisted value. There was no message to miss, because nothing had to be delivered. `BroadcastChannel` only reaches tabs that are listening at the moment a message is posted; a tab that opens later never receives what it missed, because there's nothing left to receive — the channel doesn't keep a backlog. If a new tab needs to know the *current* state rather than just future *changes* to it, something has to persist that state somewhere readable — and `localStorage` plus the `storage` event is still a reasonable way to get both the persistence and the live update in one write.\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fstorage-event-cross-tab-sync\u002Fplayground)**\n\n_Runs right in your browser — poke at it and watch the concept react live._\n\n\u003C!-- playground:end -->\n\n## Try it with two real tabs\n\nEverything above is easy to nod along to and easy to get backwards in actual code, because the bug only shows up in the tab you're not looking at — or, worse, the one you are, silently not updating while you assume it did. Open the demo below in two tabs and watch both directions at once: the tab that writes, and the tab that's told.\n\n## The part worth remembering\n\nNone of this is a `storage`-event bug. It's a one-line spec detail that's easy to miss because almost nothing else behaves this way — you change a variable, your own scope sees the new value; you call a setter, your own component re-renders. A browser event that deliberately excludes the one document that caused it is the exception, not the rule, and the only way to catch it before production is to actually check the tab you clicked in, not just the one next to it.\n\nWhat's the last cross-tab or cross-window feature you shipped where you only tested the *other* window?\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 8-question quiz →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fstorage-event-cross-tab-sync\u002Fquiz)**\n\n_Instant feedback, a hint on every question, and an explanation for each answer — right or wrong._\n\n\u003C!-- quiz:end -->\n\n\u003C!-- related:start -->\n\n## 📚 Read next\n\n- [Stop using the localStorage hack to sync browser tabs. BroadcastChannel does it natively.](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fbroadcast-channel-cross-tab-messaging)\n- [Your Timer Doesn't Know You Tabbed Away](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fpage-visibility-api-tab-aware-timers)\n- [Five tabs open, one refresh token — the race nobody noticed](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fweb-locks-api-tab-coordination)\n\n\u003C!-- related:end -->\n\n---\n\n🚀 **Want more like this?** Every guide, playground, and quiz lives on **[bestpractic.org](https:\u002F\u002Fbestpractic.org\u002F)** — open it and **[sign up free](https:\u002F\u002Fbestpractic.org\u002F)** so the next one finds you.\n\n*Thanks for reading! Let's stay connected:*\n\n- ⭐ **GitHub** — follow me and star the projects: [github.com\u002Fparsajiravand](https:\u002F\u002Fgithub.com\u002Fparsajiravand)\n- 💬 **Discord** — join the frontend best-practices community: [discord.gg\u002Fd9KRhuAwQ](https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ)\n- 📸 **Instagram** — frontend best practices, daily: [@bestpractice___](https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F)",{"title":47,"canonical":227,"description":228},"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fstorage-event-cross-tab-sync","A cross-tab 'log out everywhere' feature built on the storage event worked in every tab except the one the user actually clicked in. The storage event never fires in the tab that m","01a0f615-e1c2-729c-b09a-71da050976b3",{"id":231,"locked":18},"01a0f615-e1e2-709b-b1cf-fdd78c58b14e",[233],{"id":234,"slug":46,"title":235,"_count":236},"01a0f615-e1fb-73dd-8867-4a03ec4ca612","The storage event, cross-tab",{"questions":237},8,[239],{"locale":13,"slug":46},{"id":234,"slug":46,"title":235,"_count":241,"questionCount":237},{"questions":237},[243,245,247,249,253,257,261,265,269,271,275,279],{"slug":67,"name":68,"articles":244},126,{"slug":61,"name":65,"articles":246},104,{"slug":70,"name":71,"articles":248},80,{"slug":250,"name":251,"articles":252},"tutorial","Tutorial",49,{"slug":254,"name":255,"articles":256},"css","Css",40,{"slug":258,"name":259,"articles":260},"typescript","Typescript",18,{"slug":262,"name":263,"articles":264},"performance","Performance",16,{"slug":266,"name":267,"articles":268},"react","React",15,{"slug":73,"name":74,"articles":270},12,{"slug":272,"name":273,"articles":274},"node","Node",11,{"slug":276,"name":277,"articles":278},"html","Html",9,{"slug":280,"name":281,"articles":237},"accessibility","Accessibility",{"items":283,"meta":413},[284,296,317,335,358,376,395],{"id":45,"slug":46,"title":47,"subtitle":48,"excerpt":49,"coverUrl":50,"locale":13,"readingMinutes":51,"publishedAt":52,"viewCount":285,"likeCount":19,"commentCount":19,"author":286,"vertical":287,"topic":288,"tags":289,"_count":294,"playground":295,"hasQuiz":17,"hasPlayground":17},39,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[290,291,292,293],{"slug":61,"name":65,"color":48},{"slug":67,"name":68,"color":48},{"slug":70,"name":71,"color":48},{"slug":73,"name":74,"color":48},{"assessments":76},{"slug":46},{"id":297,"slug":298,"title":299,"subtitle":48,"excerpt":300,"coverUrl":301,"locale":13,"readingMinutes":302,"publishedAt":303,"viewCount":304,"likeCount":19,"commentCount":19,"author":305,"vertical":306,"topic":307,"tags":310,"_count":315,"playground":316,"hasQuiz":17,"hasPlayground":17},"01a0af18-2d15-7347-b2f4-6cdfa2ce56fa","vue-weekly-definemodel-v-model-contract","Vue defineModel: The v-model Contract, Explained","Vue's defineModel macro replaces the modelValue prop and update:modelValue event with one ref. The exact v-model contract behind it, verified against Vue 3.5.","\u002Fmedia\u002Fcovers\u002Fvue-weekly-definemodel-v-model-contract.png",13,"2026-10-05T06:11:02.623Z",170,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":308,"name":309},"vue","Vue",[311,312,313,314],{"slug":308,"name":309,"color":48},{"slug":61,"name":65,"color":48},{"slug":250,"name":251,"color":48},{"slug":67,"name":68,"color":48},{"assessments":76},{"slug":298},{"id":318,"slug":319,"title":320,"subtitle":48,"excerpt":321,"coverUrl":322,"locale":13,"readingMinutes":51,"publishedAt":323,"viewCount":324,"likeCount":19,"commentCount":19,"author":325,"vertical":326,"topic":327,"tags":328,"_count":333,"playground":334,"hasQuiz":17,"hasPlayground":17},"01a0f0ef-d9b8-719b-97a3-fb43e840c4d1","bfcache-beforeunload-back-button","`beforeunload` Can Still Kill Your bfcache. Mount It Only When Dirty.","A `beforeunload` listener you left mounted forever can still quietly cost you bfcache eligibility — and an `unload` listener reliably will. Here's the pagehide\u002Fpageshow fix, and why 'mount it only when there's something to lose' is the durable rule.","\u002Fmedia\u002Fcovers\u002Fbfcache-beforeunload-back-button.png","2026-10-05T06:10:30.618Z",107,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[329,330,331,332],{"slug":67,"name":68,"color":48},{"slug":61,"name":65,"color":48},{"slug":262,"name":263,"color":48},{"slug":70,"name":71,"color":48},{"assessments":76},{"slug":319},{"id":336,"slug":337,"title":338,"subtitle":48,"excerpt":339,"coverUrl":340,"locale":13,"readingMinutes":341,"publishedAt":342,"viewCount":343,"likeCount":19,"commentCount":19,"author":344,"vertical":345,"topic":346,"tags":349,"_count":356,"playground":357,"hasQuiz":17,"hasPlayground":17},"01a0f8de-661d-724c-a8c3-3aef432860c4","nuxt-weekly-ssr-streaming-route-rules","Nuxt 4.5 SSR Streaming: The Route Rules That Disable It","Nuxt 4.5's experimental SSR streaming flushes HTML early, but cache, isr, swr, redirect, and ssr:false route rules silently fall back to buffering. Cheat sheet included.","\u002Fmedia\u002Fcovers\u002Fnuxt-weekly-ssr-streaming-route-rules.png",14,"2026-10-04T12:03:48.007Z",173,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":347,"name":348},"nuxt","Nuxt",[350,351,354,355],{"slug":347,"name":348,"color":48},{"slug":352,"name":353,"color":48},"ssr","SSR",{"slug":61,"name":65,"color":48},{"slug":250,"name":251,"color":48},{"assessments":76},{"slug":337},{"id":359,"slug":360,"title":361,"subtitle":48,"excerpt":362,"coverUrl":363,"locale":13,"readingMinutes":268,"publishedAt":364,"viewCount":365,"likeCount":19,"commentCount":19,"author":366,"vertical":367,"topic":368,"tags":369,"_count":374,"playground":375,"hasQuiz":17,"hasPlayground":17},"01a0f615-d2be-750b-839e-02fd75291b4e","react-weekly-viewtransition-component","React 19.3 ViewTransition: Animate State Without Losing It","React 19.3's ViewTransition component animates state changes, not just page swaps — the mental model, enter\u002Fexit\u002Fupdate\u002Fshare, and a cheat sheet.","\u002Fmedia\u002Fcovers\u002Freact-weekly-viewtransition-component.png","2026-10-03T11:22:26.437Z",235,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":266,"name":267},[370,371,372,373],{"slug":266,"name":267,"color":48},{"slug":61,"name":65,"color":48},{"slug":250,"name":251,"color":48},{"slug":67,"name":68,"color":48},{"assessments":76},{"slug":360},{"id":377,"slug":378,"title":379,"subtitle":48,"excerpt":380,"coverUrl":381,"locale":13,"readingMinutes":382,"publishedAt":383,"viewCount":384,"likeCount":19,"commentCount":19,"author":385,"vertical":386,"topic":387,"tags":388,"_count":393,"playground":394,"hasQuiz":17,"hasPlayground":17},"01a0ec73-247c-7738-84ae-80c9d1d116a8","input-lag-bug-debounce-cant-fix","Your Search Box Lags. Debounce Hides It; `useTransition` Fixes It","A big list re-render was blocking every keystroke — debouncing just moved the freeze. useTransition tells React which update can wait.","\u002Fmedia\u002Fcovers\u002Finput-lag-bug-debounce-cant-fix.png",5,"2026-10-03T11:21:55.564Z",197,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":266,"name":267},[389,390,391,392],{"slug":266,"name":267,"color":48},{"slug":61,"name":65,"color":48},{"slug":67,"name":68,"color":48},{"slug":262,"name":263,"color":48},{"assessments":76},{"slug":378},{"id":396,"slug":397,"title":398,"subtitle":48,"excerpt":399,"coverUrl":400,"locale":13,"readingMinutes":270,"publishedAt":401,"viewCount":402,"likeCount":19,"commentCount":19,"author":403,"vertical":404,"topic":405,"tags":406,"_count":411,"playground":412,"hasQuiz":17,"hasPlayground":17},"01a08af7-9d6f-74df-b76e-f6c445f54a47","vue-weekly-computed-caching-invalidation","Vue computed(): What It Caches and When It Reruns","Vue's computed() caches its result and only reruns when a tracked dependency changes, not on every read. The exact caching and invalidation model, verified against Vue 3.5.","\u002Fmedia\u002Fcovers\u002Fvue-weekly-computed-caching-invalidation.png","2026-09-30T12:10:53.362Z",246,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":308,"name":309},[407,408,409,410],{"slug":308,"name":309,"color":48},{"slug":61,"name":65,"color":48},{"slug":250,"name":251,"color":48},{"slug":67,"name":68,"color":48},{"assessments":76},{"slug":397},{"page":76,"perPage":414,"total":246,"totalPages":268},7,"\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003Cdiv>11\u003C\u002Fdiv>\u003Cdiv>12\u003C\u002Fdiv>\u003Cdiv>13\u003C\u002Fdiv>\u003Cdiv>14\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">applyLogout\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>loggedIn\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">if\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">!\u003C\u002Fspan>loggedIn\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    \u003Cspan class=\"shj-func\">renderLoggedOut\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\nwindow\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">addEventListener\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"storage\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>e\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">if\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>e\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>key \u003Cspan class=\"shj-oper\">!==\u003C\u002Fspan> \u003Cspan class=\"shj-str\">\"session\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> loggedIn \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-num\">JSON\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">parse\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>e\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>newValue\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-func\">applyLogout\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>loggedIn\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F somewhere in the logout button's click handler:\n\u003C\u002Fspan>localStorage\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">setItem\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"session\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-num\">JSON\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">stringify\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> loggedIn\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bool\">false\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003Cdiv>11\u003C\u002Fdiv>\u003Cdiv>12\u003C\u002Fdiv>\u003Cdiv>13\u003C\u002Fdiv>\u003Cdiv>14\u003C\u002Fdiv>\u003Cdiv>15\u003C\u002Fdiv>\u003Cdiv>16\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">applyLogout\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>loggedIn\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">if\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">!\u003C\u002Fspan>loggedIn\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    \u003Cspan class=\"shj-func\">renderLoggedOut\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\nwindow\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">addEventListener\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"storage\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>e\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">if\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>e\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>key \u003Cspan class=\"shj-oper\">!==\u003C\u002Fspan> \u003Cspan class=\"shj-str\">\"session\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> loggedIn \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-num\">JSON\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">parse\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>e\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>newValue\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-func\">applyLogout\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>loggedIn\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F every OTHER tab arrives here\n\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\n\u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">logout\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  localStorage\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">setItem\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"session\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-num\">JSON\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">stringify\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> loggedIn\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bool\">false\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-func\">applyLogout\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bool\">false\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F THIS tab has to call itself — no event is coming\n\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>",{"locked":18,"total":19,"comments":418},[]]