From c7f58343b7225bcfe0e6159dbcaae24724587e28 Mon Sep 17 00:00:00 2001 From: ump45nose Date: Mon, 5 Oct 2026 05:19:56 +0800 Subject: [PATCH 1/3] docs(idbdatabase): explain why versionchange must close the database The `versionchange` event page only said the event was "requested elsewhere" and its examples just logged a message. It never said that the upgrade is blocked until every other connection closes, or that an open connection running an older schema is what makes running two versions of the page in separate tabs corrupt data. Add the multi-tab conflict explanation to the event page, covering `blocked` on the requesting tab and why the usual response is to close the connection and reload. Replace the two log-only examples with one showing `db.close()` in the handler and one showing `onblocked` on the requesting side. Also fix the `IDBDatabase` interface page's one-line `versionchange` description, which had the same "requested elsewhere" wording. Fixes https://github.com/mdn/content/issues/26639 --- files/en-us/web/api/idbdatabase/index.md | 2 +- .../idbdatabase/versionchange_event/index.md | 80 ++++++++----------- 2 files changed, 36 insertions(+), 46 deletions(-) diff --git a/files/en-us/web/api/idbdatabase/index.md b/files/en-us/web/api/idbdatabase/index.md index 2d203f8d8cb7aa6..0337ff92595d66e 100644 --- a/files/en-us/web/api/idbdatabase/index.md +++ b/files/en-us/web/api/idbdatabase/index.md @@ -44,7 +44,7 @@ Listen to these events using `addEventListener()` or by assigning an event liste - : An event fired when the database connection is unexpectedly closed. - [`versionchange`](/en-US/docs/Web/API/IDBDatabase/versionchange_event) - - : An event fired when a database structure change was requested. + - : An event fired when another connection requested a structure change (an upgrade) or a deletion. Handler code should [close the database](/en-US/docs/Web/API/IDBDatabase/versionchange_event) so that the change is not blocked, otherwise running two versions of the page in different tabs can read and write the wrong schema. The following events are available to `IDBDatabase` via event bubbling from {{domxref("IDBTransaction")}}: diff --git a/files/en-us/web/api/idbdatabase/versionchange_event/index.md b/files/en-us/web/api/idbdatabase/versionchange_event/index.md index b97b1f69291e19c..987c25a75374738 100644 --- a/files/en-us/web/api/idbdatabase/versionchange_event/index.md +++ b/files/en-us/web/api/idbdatabase/versionchange_event/index.md @@ -8,17 +8,22 @@ browser-compat: api.IDBDatabase.versionchange_event {{APIRef("IndexedDB")}} -The `versionchange` event is fired when a database structure change ([`upgradeneeded`](/en-US/docs/Web/API/IDBOpenDBRequest/upgradeneeded_event) event send on an [`IDBOpenDBRequest`](/en-US/docs/Web/API/IDBOpenDBRequest) or [`IDBFactory.deleteDatabase`](/en-US/docs/Web/API/IDBFactory/deleteDatabase)) was requested elsewhere (most probably in -another window/tab on the same computer). +The `versionchange` event is fired when a database structure change ([`upgradeneeded`](/en-US/docs/Web/API/IDBOpenDBRequest/upgradeneeded_event) event sent on an [`IDBOpenDBRequest`](/en-US/docs/Web/API/IDBOpenDBRequest)) or a deletion ([`IDBFactory.deleteDatabase`](/en-US/docs/Web/API/IDBFactory/deleteDatabase)) was requested from another connection — most probably another window or tab of the same page on the same computer. + +Because the browser cannot change the schema while other connections are open, the requested upgrade or deletion is **blocked** until every other open connection is closed. Your `versionchange` handler is therefore the only place where you can make that happen; if you do not close the connection from it, the page that requested the change is stuck until the user closes every tab that still has the database open. + +An open connection whose code knows an older schema is also a correctness hazard: it can keep reading and writing using the structure it was written against, so a change made by another tab can silently be read back as corrupt or missing data. Handling `versionchange` by closing the connection promptly is what keeps the two tabs from stepping on each other. + +The `open` request in the other tab receives [`blocked`](/en-US/docs/Web/API/IDBOpenDBRequest/blocked_event) for as long as your connection stays open. Once you have closed it, that tab's [`upgradeneeded`](/en-US/docs/Web/API/IDBOpenDBRequest/upgradeneeded_event) runs and the new version is in effect. Because the schema can move on while your tab is still running the old code, it is also worth handling [`VersionError`](/en-US/docs/Web/API/IDBRequest/error_event) — it is raised when an attempt is made to [open the database](/en-US/docs/Web/API/IDBFactory/open) with a version number that is now outdated. ## Syntax Use the event name in methods like {{domxref("EventTarget.addEventListener", "addEventListener()")}}, or set an event handler property. ```js-nolint -addEventListener("versionchange", (event) => { }) +addEventListener("versionchange", (event) => { }); -onversionchange = (event) => { } +onversionchange = (event) => { }; ``` ## Event type @@ -27,64 +32,49 @@ A generic {{domxref("Event")}}. ## Examples -This example opens a database and, on success, adds a listener to `versionchange`: +The important thing a `versionchange` handler can do is close the database, so that the tab that asked for the change is unblocked. A common pattern is to close the connection and tell the user to reload: ```js -// Open the database -const dBOpenRequest = window.indexedDB.open("Nonexistent", 4); +const dbOpenRequest = window.indexedDB.open("toDoList", 1); -dBOpenRequest.onupgradeneeded = (event) => { +dbOpenRequest.onsuccess = (event) => { const db = event.target.result; - // Create an objectStore for this database - const objectStore = db.createObjectStore("toDoList", { - keyPath: "taskTitle", - }); - - // define what data items the objectStore will contain - objectStore.createIndex("hours", "hours", { unique: false }); - objectStore.createIndex("minutes", "minutes", { unique: false }); - objectStore.createIndex("day", "day", { unique: false }); - objectStore.createIndex("month", "month", { unique: false }); - objectStore.createIndex("year", "year", { unique: false }); -}; -dBOpenRequest.addEventListener("success", (event) => { - const db = event.target.result; - db.addEventListener("versionchange", (event) => { - console.log("The version of this database has changed"); - }); -}); + db.onversionchange = () => { + // Another tab asked for a newer version. Close our connection so the + // other tab's upgradeneeded can run, then ask the user to reload. + db.close(); + document.querySelector("p").textContent = + "A new version of this page is ready. Please reload or close this tab."; + }; +}; ``` -The same example, using the `onversionchange` event handler property: +A tab that requests the change should show the state of the request to the user, so that a block on another tab is not mistaken for a failure: ```js -// Open the database -const dBOpenRequest = window.indexedDB.open("Nonexistent", 4); +const dbOpenRequest = window.indexedDB.open("toDoList", 2); + +// Another tab still holds the database open and has not yet run its +// versionchange handler, so the upgrade is deferred. +dbOpenRequest.onblocked = () => { + document.querySelector("p").textContent = + "Still waiting for other tabs to close the database."; +}; -dBOpenRequest.onupgradeneeded = (event) => { +dbOpenRequest.onupgradeneeded = (event) => { const db = event.target.result; - // Create an objectStore for this database - const objectStore = db.createObjectStore("toDoList", { - keyPath: "taskTitle", - }); - - // define what data items the objectStore will contain - objectStore.createIndex("hours", "hours", { unique: false }); - objectStore.createIndex("minutes", "minutes", { unique: false }); - objectStore.createIndex("day", "day", { unique: false }); - objectStore.createIndex("month", "month", { unique: false }); - objectStore.createIndex("year", "year", { unique: false }); + db.createObjectStore("toDoList", { keyPath: "taskTitle" }); }; -dBOpenRequest.onsuccess = (event) => { +dbOpenRequest.onsuccess = (event) => { const db = event.target.result; - db.onversionchange = (event) => { - console.log("The version of this database has changed"); - }; + document.querySelector("p").textContent = "Version " + db.version; }; ``` +For the full account of how this fits into an app's lifecycle, including what happens to stale connections, see the [Using IndexedDB](/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB) guide. + ## Specifications {{Specifications}} From e14b6810889228d7172ab83dce5a700fc07c9b15 Mon Sep 17 00:00:00 2001 From: ump45nose <52391318+ump45nose@users.noreply.github.com> Date: Sat, 10 Oct 2026 17:23:56 +0800 Subject: [PATCH 2/3] versionchange: keep to behavior defined by the spec --- .../api/idbdatabase/versionchange_event/index.md | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/files/en-us/web/api/idbdatabase/versionchange_event/index.md b/files/en-us/web/api/idbdatabase/versionchange_event/index.md index 987c25a75374738..32d32e870e7190e 100644 --- a/files/en-us/web/api/idbdatabase/versionchange_event/index.md +++ b/files/en-us/web/api/idbdatabase/versionchange_event/index.md @@ -10,20 +10,18 @@ browser-compat: api.IDBDatabase.versionchange_event The `versionchange` event is fired when a database structure change ([`upgradeneeded`](/en-US/docs/Web/API/IDBOpenDBRequest/upgradeneeded_event) event sent on an [`IDBOpenDBRequest`](/en-US/docs/Web/API/IDBOpenDBRequest)) or a deletion ([`IDBFactory.deleteDatabase`](/en-US/docs/Web/API/IDBFactory/deleteDatabase)) was requested from another connection — most probably another window or tab of the same page on the same computer. -Because the browser cannot change the schema while other connections are open, the requested upgrade or deletion is **blocked** until every other open connection is closed. Your `versionchange` handler is therefore the only place where you can make that happen; if you do not close the connection from it, the page that requested the change is stuck until the user closes every tab that still has the database open. +An upgrade or deletion cannot proceed while other connections to the database are open: it waits until every other connection has been closed. Handling `versionchange` by calling {{domxref("IDBDatabase.close()")}} is the usual way to let it proceed; if the connection stays open, the request in the other tab stays pending until the connection is closed, for example when the user closes the tab. -An open connection whose code knows an older schema is also a correctness hazard: it can keep reading and writing using the structure it was written against, so a change made by another tab can silently be read back as corrupt or missing data. Handling `versionchange` by closing the connection promptly is what keeps the two tabs from stepping on each other. - -The `open` request in the other tab receives [`blocked`](/en-US/docs/Web/API/IDBOpenDBRequest/blocked_event) for as long as your connection stays open. Once you have closed it, that tab's [`upgradeneeded`](/en-US/docs/Web/API/IDBOpenDBRequest/upgradeneeded_event) runs and the new version is in effect. Because the schema can move on while your tab is still running the old code, it is also worth handling [`VersionError`](/en-US/docs/Web/API/IDBRequest/error_event) — it is raised when an attempt is made to [open the database](/en-US/docs/Web/API/IDBFactory/open) with a version number that is now outdated. +If connections are still open after the `versionchange` events have been dispatched, the request that asked for the change receives a [`blocked`](/en-US/docs/Web/API/IDBOpenDBRequest/blocked_event) event. Once the remaining connections are closed, an upgrade continues with that request's [`upgradeneeded`](/en-US/docs/Web/API/IDBOpenDBRequest/upgradeneeded_event) event. Code that keeps running after closing its connection should also expect that reopening the database with its old version number now fails with a `VersionError`, because the requested version is lower than the database's current version. ## Syntax Use the event name in methods like {{domxref("EventTarget.addEventListener", "addEventListener()")}}, or set an event handler property. ```js-nolint -addEventListener("versionchange", (event) => { }); +addEventListener("versionchange", (event) => { }) -onversionchange = (event) => { }; +onversionchange = (event) => { } ``` ## Event type @@ -55,8 +53,8 @@ A tab that requests the change should show the state of the request to the user, ```js const dbOpenRequest = window.indexedDB.open("toDoList", 2); -// Another tab still holds the database open and has not yet run its -// versionchange handler, so the upgrade is deferred. +// Another tab still has the database open (it did not close its connection +// in response to versionchange), so the upgrade has to wait. dbOpenRequest.onblocked = () => { document.querySelector("p").textContent = "Still waiting for other tabs to close the database."; @@ -73,7 +71,7 @@ dbOpenRequest.onsuccess = (event) => { }; ``` -For the full account of how this fits into an app's lifecycle, including what happens to stale connections, see the [Using IndexedDB](/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB) guide. +See also [Version changes while a web app is open in another tab](/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB#version_changes_while_a_web_app_is_open_in_another_tab) in the Using IndexedDB guide. ## Specifications From 4d92d5d75d93dedf55a16d3101c9567b09e4b245 Mon Sep 17 00:00:00 2001 From: ump45nose <52391318+ump45nose@users.noreply.github.com> Date: Sat, 10 Oct 2026 17:23:58 +0800 Subject: [PATCH 3/3] IDBDatabase: drop unsourced schema claim --- files/en-us/web/api/idbdatabase/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/en-us/web/api/idbdatabase/index.md b/files/en-us/web/api/idbdatabase/index.md index 0337ff92595d66e..491245d2d8dcdc7 100644 --- a/files/en-us/web/api/idbdatabase/index.md +++ b/files/en-us/web/api/idbdatabase/index.md @@ -44,7 +44,7 @@ Listen to these events using `addEventListener()` or by assigning an event liste - : An event fired when the database connection is unexpectedly closed. - [`versionchange`](/en-US/docs/Web/API/IDBDatabase/versionchange_event) - - : An event fired when another connection requested a structure change (an upgrade) or a deletion. Handler code should [close the database](/en-US/docs/Web/API/IDBDatabase/versionchange_event) so that the change is not blocked, otherwise running two versions of the page in different tabs can read and write the wrong schema. + - : An event fired when another connection requested a structure change (an upgrade) or a deletion. Handler code should [close the database](/en-US/docs/Web/API/IDBDatabase/versionchange_event) so that the change is not blocked. The following events are available to `IDBDatabase` via event bubbling from {{domxref("IDBTransaction")}}: