<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<!DOCTYPE bugzilla SYSTEM "https://bugs.webkit.org/page.cgi?id=bugzilla.dtd">

<bugzilla version="5.0.4.1"
          urlbase="https://bugs.webkit.org/"
          
          maintainer="admin@webkit.org"
>

    <bug>
          <bug_id>324058</bug_id>
          
          <creation_ts>2026-09-12 12:47:13 -0700</creation_ts>
          <short_desc>IndexedDB transactions never start after cross-site back navigation when a service worker controls the page (regression from bug 322386)</short_desc>
          <delta_ts>2026-09-17 19:11:14 -0700</delta_ts>
          <reporter_accessible>1</reporter_accessible>
          <cclist_accessible>1</cclist_accessible>
          <classification_id>1</classification_id>
          <classification>Unclassified</classification>
          <product>WebKit</product>
          <component>Website Storage</component>
          <version>WebKit Nightly Build</version>
          <rep_platform>All</rep_platform>
          <op_sys>All</op_sys>
          <bug_status>RESOLVED</bug_status>
          <resolution>FIXED</resolution>
          
          <see_also>https://bugs.webkit.org/show_bug.cgi?id=322386</see_also>
          <bug_file_loc></bug_file_loc>
          <status_whiteboard></status_whiteboard>
          <keywords>InRadar</keywords>
          <priority>P2</priority>
          <bug_severity>Major</bug_severity>
          <target_milestone>---</target_milestone>
          
          
          <everconfirmed>1</everconfirmed>
          <reporter name="Ashraf Ali">ashraf.iu.cse1718</reporter>
          <assigned_to name="Devin Rousso">hi</assigned_to>
          <cc>ahmad.saleem792</cc>
    
    <cc>sihui_liu</cc>
    
    <cc>webkit-bug-importer</cc>
          

      

      

      

          <comment_sort_order>oldest_to_newest</comment_sort_order>  
          <long_desc isprivate="0" >
    <commentid>2249868</commentid>
    <comment_count>0</comment_count>
    <who name="Ashraf Ali">ashraf.iu.cse1718</who>
    <bug_when>2026-09-12 12:47:13 -0700</bug_when>
    <thetext>After a page with a controlling service worker navigates cross-site and the user navigates back, IDBFactory.open() succeeds but the first transaction on the new connection never runs - its requests stay pending indefinitely. Without a service worker, or before commit 03efc9761a39, the same round trip works.

STEPS TO REPRODUCE (self-contained, no external network needed):

1. npm install @playwright/test@1.63.0 &amp;&amp; npx playwright install webkit (this build is WebKit 26.6 / r2359; current main r2361 is also affected)
2. Save the script below as repro.mjs and run: node repro.mjs
3. Observe: it prints {&quot;result&quot;:&quot;FAIL&quot;,&quot;phase&quot;:&quot;read after back navigation&quot;} - after goBack(), the page&apos;s #status stays at &quot;loading&quot; because the read transaction never completes within 7s.
4. Controls: node repro.mjs --no-sw (no service worker) prints PASS, and @playwright/test@1.62.1 (WebKit 26.5) prints PASS with the worker.

--- repro.mjs ---
// Standalone reproduction. Loopback HTTP server, IndexedDB with one key,
// and a service worker whose only handler is activate -&gt; clients.claim().
// The https://example.test/ destination is fulfilled via route interception,
// so nothing leaves the machine.
import { createServer } from &apos;node:http&apos;;
import { createRequire } from &apos;node:module&apos;;
const require = createRequire(import.meta.url);
const packagePath = process.env.PLAYWRIGHT_PACKAGE || &apos;@playwright/test&apos;;
const { webkit, expect } = require(packagePath);
const withWorker = !process.argv.includes(&apos;--no-sw&apos;);
const html = `&lt;!doctype html&gt;&lt;html&gt;&lt;body&gt;
&lt;p id=&quot;status&quot;&gt;loading&lt;/p&gt;&lt;a href=&quot;https://example.test/&quot;&gt;Other site&lt;/a&gt;
&lt;script type=&quot;module&quot;&gt;
const ready = new Promise((resolve, reject) =&gt; {
  const request = indexedDB.open(&apos;synthetic-roundtrip&apos;, 1);
  request.onupgradeneeded = () =&gt; request.result.createObjectStore(&apos;items&apos;);
  request.onsuccess = () =&gt; resolve(request.result);
  request.onerror = () =&gt; reject(request.error);
});
async function transaction(value) {
  const db = await ready;
  return new Promise((resolve, reject) =&gt; {
    const tx = db.transaction(&apos;items&apos;, &apos;readwrite&apos;);
    const request = tx.objectStore(&apos;items&apos;).get(&apos;name&apos;);
    request.onsuccess = () =&gt; {
      if (value) tx.objectStore(&apos;items&apos;).put(value, &apos;name&apos;);
    };
    tx.oncomplete = () =&gt; resolve(request.result);
    tx.onerror = tx.onabort = () =&gt; reject(tx.error);
  });
}
transaction().then(() =&gt; { document.querySelector(&apos;#status&apos;).textContent = &apos;ready&apos;; });
window.save = () =&gt; transaction(&apos;Synthetic Player&apos;);
window.read = () =&gt; transaction();
${withWorker ? &quot;addEventListener(&apos;load&apos;, () =&gt; navigator.serviceWorker.register(&apos;/worker.js&apos;));&quot; : &apos;&apos;}
&lt;/script&gt;&lt;/body&gt;&lt;/html&gt;`;
const server = createServer((request, response) =&gt; {
  if (request.url === &apos;/worker.js&apos;) {
    response.writeHead(200, { &apos;Content-Type&apos;: &apos;application/javascript&apos; });
    // No fetch handler, cache, IndexedDB, or background work in this worker.
    response.end(&quot;self.addEventListener(&apos;activate&apos;, event =&gt; event.waitUntil(self.clients.claim()));&quot;);
  } else {
    response.writeHead(200, { &apos;Content-Type&apos;: &apos;text/html&apos; });
    response.end(html);
  }
});
await new Promise(resolve =&gt; server.listen(0, &apos;127.0.0.1&apos;, resolve));
const browser = await webkit.launch();
const page = await browser.newPage();
const base = `http://127.0.0.1:${server.address().port}/`;
let phase = &apos;initial read&apos;;
try {
  await page.route(&apos;https://example.test/&apos;, route =&gt; route.fulfill({
    contentType: &apos;text/html&apos;, body: &apos;&lt;h1&gt;Local synthetic destination&lt;/h1&gt;&apos;
  }));
  await page.goto(base);
  await expect(page.locator(&apos;#status&apos;)).toHaveText(&apos;ready&apos;, { timeout: 7000 });
  if (withWorker) {
    await page.evaluate(() =&gt; navigator.serviceWorker.ready);
    await page.waitForFunction(() =&gt; navigator.serviceWorker.controller, null, { timeout: 7000 });
  }
  await page.evaluate(() =&gt; window.save());
  await page.locator(&apos;a&apos;).press(&apos;Enter&apos;);
  await expect(page).toHaveURL(&apos;https://example.test/&apos;);
  phase = &apos;read after back navigation&apos;;
  await page.goBack();
  await expect(page.locator(&apos;#status&apos;)).toHaveText(&apos;ready&apos;, { timeout: 7000 });
  expect(await page.evaluate(() =&gt; window.read())).toBe(&apos;Synthetic Player&apos;);
  console.log(JSON.stringify({ result: &apos;PASS&apos;, engine: browser.version(), withWorker, phase }));
} catch (error) {
  console.log(JSON.stringify({ result: &apos;FAIL&apos;, engine: browser.version(), withWorker, phase,
    message: error.message.split(&apos;\n&apos;).slice(0, 7).join(&apos; &apos;) }));
  process.exitCode = 1;
} finally {
  await browser.close();
  await new Promise(resolve =&gt; server.close(resolve));
}
--- end repro.mjs ---

EXPECTED: after the cross-site round trip, the transaction completes and returns the previously saved value.

ACTUAL: after navigating back, database opening succeeds but the transaction never completes; the page stays at &quot;loading&quot;.

Additional controls that still fail after back navigation (i.e. they do not rescue it):
- 127.0.0.1 -&gt; localhost as the destination
- history.back() instead of page.goBack()
- Explicitly closing the database before leaving the page
- Waiting for serviceWorker.ready before opening the database

BISECT across published Playwright WebKit builds (reproduced identically on Windows and Linux):

  build   WebKit  service worker   result
  r2336   26.5    yes              PASS
  r2355   26.5    yes              PASS
  r2358   26.6    yes              FAIL
  r2359   26.6    yes              FAIL
  r2359   26.6    no               PASS
  r2361   26.6    yes              FAIL   (WebKit main as of Sep 2026)

SUSPECT: commit 03efc9761a39 (bug 322386, landed 2026-08-26, inside the last-good/first-bad window). That commit changed UniqueIDBDatabase scheduling so takeNextRunnableTransaction() no longer picks transactions of suspended clients - they are kept queued until the client resumes, with resume handled via setClientSuspended(false) from the WebProcess. This scenario has all the ingredients: the cross-site navigation parks the page&apos;s client as suspended (back/forward cache), the page is restored on back navigation, and with a controlling service worker the queued transaction apparently never gets scheduled again - consistent with the resume notification never reaching the storage scheduler on this path. Without the worker the same round trip passes, so the plain resume path works.

Original report and discussion: https://github.com/microsoft/playwright/issues/42694</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>2250160</commentid>
    <comment_count>1</comment_count>
    <who name="Radar WebKit Bug Importer">webkit-bug-importer</who>
    <bug_when>2026-09-13 22:53:22 -0700</bug_when>
    <thetext>&lt;rdar://problem/187349702&gt;</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>2250764</commentid>
    <comment_count>2</comment_count>
    <who name="Devin Rousso">hi</who>
    <bug_when>2026-09-15 12:22:49 -0700</bug_when>
    <thetext>Pull request: https://github.com/WebKit/WebKit/pull/74082</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>2251696</commentid>
    <comment_count>3</comment_count>
    <who name="EWS">ews-feeder</who>
    <bug_when>2026-09-17 19:11:12 -0700</bug_when>
    <thetext>Committed 321356@main (497098e97dcc): &lt;https://commits.webkit.org/321356@main&gt;

Reviewed commits have been landed. Closing PR #74082 and removing active labels.</thetext>
  </long_desc>
      
      

    </bug>

</bugzilla>