Safari Uploads an Empty File? Check Service Worker Control
Your page reads the selected file and reports the right size, yet the server gets an empty body from Safari. What does a service worker have to do with it?

- A valid File.size does not establish that its contents reached the server.
- Record service worker control and where the File came from.
- Technology Preview 255’s fix does not establish deployment to every stable Safari build.
Compare the selected file with the received payload
If Safari sends an empty upload, check the page’s service worker state before changing server limits. Technology Preview 255 lists a fix for disk-backed File bodies disappearing from fetch and XMLHttpRequest uploads on controlled pages.
The controller property identifies the worker controlling the current page. That is more specific than asking whether the application has ever registered one. A console check is:
console.log(navigator.serviceWorker?.controller?.scriptURL ?? null); A script URL identifies the controller; it does not prove the worker caused the failure. Record it alongside the selected file’s size and the server’s received-byte count.
A null controller does not rule out file-handling failures
Bug 319985 includes reports of readable files producing empty uploads. Later reports describe Files restored from IndexedDB without a service worker, so keep the file’s origin in the investigation.
The fix’s commit message describes both a check blocking files in the initiating process’s IndexedDB data directory and an iOS sandbox issue with file-read extensions for the service worker process. Changing fetch to XHR is not an established solution.
Change one condition at a time
Use the failing request as a baseline. arrayBuffer() reads the contents into a binary buffer, allowing a separate check that the page can read the bytes. It does not test delivery.
| Record | What it distinguishes |
|---|---|
| Picker, drop or IndexedDB origin | The route by which the File entered the upload flow. |
| Current controller | Whether this page is controlled. |
| File.size and the buffer’s byteLength | Availability of the contents before transmission. |
| Received request and file-part sizes | Actual delivery, independent of the page’s file object. |
Keep the application’s request format when comparing cases. A multipart body includes boundaries and fields, so its total size is not expected to equal the file size. Compare the received file part separately where possible.
Test a memory-copy workaround at the intended file size
A reporter found that sending new Blob([await file.arrayBuffer()], { type: file.type }) worked around the problem. It reads the file into memory, making file size part of the decision to adopt it.
Technology Preview is a macOS test browser. Its release note is not proof that an iPhone or a stable Safari installation contains the fix. Close the production issue when the affected environment delivers the complete file, rather than when the picker shows a plausible size.