Skip to content

fix(lpa-fs-opfs): copy bytes before OPFS write — WebKit writes the whole buffer for wasm-memory views#117

Open
Yona-Appletree wants to merge 1 commit into
mainfrom
claude/webkit-opfs-view-write
Open

fix(lpa-fs-opfs): copy bytes before OPFS write — WebKit writes the whole buffer for wasm-memory views#117
Yona-Appletree wants to merge 1 commit into
mainfrom
claude/webkit-opfs-view-write

Conversation

@Yona-Appletree

Copy link
Copy Markdown
Member

Summary

Root cause of the iOS Safari crash loop ("A problem repeatedly occurred") on a fresh visit, and of every zero-filled library file seen so far (the missing session: … parse meta: expected value at line 1 column 1 from before):

  • WebKit's FileSystemWritableFileStream.write() ignores a TypedArray view's byteOffset/length and writes the view's entire underlying ArrayBuffer. Verified on WebKit 26.5 with a pure-JS probe: a 1000-byte view over WebAssembly.Memory produced a 65,536-byte file (whole wasm page), with or without memory growth. Chrome honors the view.
  • web-sys's write_with_u8_array(&[u8]) passes exactly such a view over wasm linear memory. So on WebKit, every flushed file received the app's entire wasm heap: file contents start at heap byte 0 (zeros → expected value at line 1 column 1), and seeding a fresh library serialized hundreds of MB per file → memory/IO spike → iOS jetsam → crash on every visit.
  • Reproduced against production (lightplayer.app) with Playwright WebKit (iPhone 15 Pro profile): fresh profile, click the example → skipping package dir …: manifest: parse project.json: expected value at line 1 column 1 and ~1.4 GB RSS on desktop.

Fixes (two layers):

  1. write_file copies into a JS-owned Uint8Array before write(). The copy's buffer is exactly the payload, so both the spec-correct and the WebKit interpretation persist the right bytes.
  2. load_tree skips files over 16 MiB (library content is device-bound — kilobytes; the only known producer of bigger files is the pre-fix heap dumps). Without this, a library already poisoned by the old build re-OOMs the tab at mount even after the write fix — this is what actually un-bricks phones with existing damage.

Context: PR #116 (lenient meta + husk cleanup) treated real but secondary symptoms; this is the primary defect. Consider an upstream WebKit bug report — write() truncating BufferSource views to their view bounds is spec behavior.

Validation

  • Pure-JS WebKit round-trip probe: plain array OK; wasm-memory view → whole-buffer write (bug pinned; probe script in the session, reproducible in Playwright WebKit 26.5).
  • End-to-end on WebKit (iPhone profile), fixed bundle served locally: seed → flush → every file byte-exact (427 B project.json etc.), all JSON parses, project opens, survives reload.
  • Poisoned-store scenario (27 files × 48 MiB of NULs, mimicking a damaged phone): mount skips all dumps with warnings, gallery loads, no OOM, no crash.
  • Chrome OPFS suite (just lpa-fs-opfs-test): 20 tests green, including 2 new regression tests (exact-length round trip; oversized-file skip). Note the WebKit-specific behavior is not exercisable in CI's chromedriver lane — a WebKit lane is a worthwhile follow-up.
  • just check clean.

Contribution Terms

By opening this pull request, I confirm that I have the right to submit these
changes and agree to the contribution terms in CONTRIBUTING.md.

🤖 Generated with Claude Code

…buffer for wasm views

Root cause of the iOS Safari studio crash ("A problem repeatedly
occurred") and of every zero-filled library file: WebKit's
FileSystemWritableFileStream.write() ignores a TypedArray view's
offset/length and writes the view's ENTIRE underlying buffer (verified
on WebKit 26.5; Chrome honors the view). web-sys write_with_u8_array
passes exactly such a view over wasm linear memory, so on WebKit every
flushed file received the whole wasm heap: contents start at heap byte
0 (zeros -> "expected value at line 1 column 1"), and seeding a fresh
library serialized hundreds of MB per file -> memory spike -> iOS
jetsam -> crash loop on a fresh visit. Reproduced against production
with Playwright WebKit (iPhone profile) and pinned with a pure-JS
round-trip probe (1000-byte wasm-memory view -> 65536-byte file even
without memory growth).

Fixes:
- write_file copies into a JS-owned Uint8Array before write(); the
  copy's buffer is exactly the payload, so either read interpretation
  persists the right bytes. Verified byte-exact on WebKit end-to-end
  (seed -> flush -> read back, all files parse, project reopens).
- load_tree skips files over 16 MiB (no legitimate package member gets
  close; the only known producer is the pre-fix heap dumps). Without
  this, libraries already poisoned by the old build OOM the tab again
  at mount. Verified: a store with 27 x 48 MiB dumps now mounts clean.

Chrome OPFS suite: 20 tests green (2 new: exact-length round trip,
oversized-file skip).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant