Why Large Files Crash Browser Tabs (and How Streaming Fixes It)

By ShareDrop.org Engineering · Published · Updated

Ask anyone who has built browser file transfer: small files are trivial, and large files are where tabs die. The crashes trace back to one root cause, buffering an entire file in memory, showing up in three different places. This article covers all three, the browser APIs that exist to avoid them, the parameters ShareDrop.org settled on and why, and what the receiving side can and cannot do on each browser.

Failure point 1: reading

The naive approach reads the whole file into an ArrayBuffer. A 4 GB video on a laptop with 8 GB of RAM ends the story right there. The fix is streaming: file.stream() yields chunks on demand, so the amount of file in memory at any moment stays constant regardless of file size. Every browser has supported this for years; the failure is a design choice, not a platform limit.

Failure point 2: sending faster than the network

A disk reads at hundreds of MB/s; a typical uplink carries a fraction of that. Push chunks into a WebRTC data channel as fast as you read them and the channel's internal send buffer absorbs the difference — growing until the tab is killed. The standard fix is backpressure via bufferedAmount: pause reading when the buffered amount crosses a high-water mark, resume on the bufferedamountlow event. ShareDrop.org pauses at 8 MiB and resumes at 1 MiB, so sender memory stays bounded whether the file is 10 MB or 100 GB, and whether the link is fiber or hotel Wi-Fi.

Line chart of sender bufferedAmount over time with backpressure: the buffer rises quickly to the 8 MiB high-water mark, reading pauses, the buffer drains at network speed to the 1 MiB low-water mark, and reading resumes — a sawtooth that never exceeds 8 MiB
Sender-side bufferedAmount over time with ShareDrop.org's production watermarks. Disk fills the buffer in moments; the network drains it slowly; the sawtooth never exceeds 8 MiB no matter how large the file is.

Why 8 and 1

The high-water mark is a bandwidth-delay calculation. On a fast LAN the pipeline moves about 25 MB/s, so 8 MiB is roughly a third of a second of data queued ahead — enough that a brief hiccup in disk reads never starves the network, and far more than a typical round trip needs to keep the link full. Larger buys nothing except memory and a longer wait when the user cancels. The low-water mark of 1 MiB leaves a comfortable amount still in flight when reading resumes, so the network never idles between refills. On a 30 Mbps uplink the same 8 MiB is two seconds of data — still bounded, still fine. The values are not magic; they are sized so that even a fast LAN link stays saturated between refills.

Failure point 3: receiving

The receiver has the hardest problem: browsers historically offered no way to write a download progressively — you accumulated Blobs in memory and triggered a download at the end. That works until it doesn't, and “doesn't” arrives at different sizes on different devices. Several paths exist today, with very different support:

ApproachWhere it worksMemoryCatch
File System Access API (showSaveFilePicker, showDirectoryPicker)Chrome and Edge on desktopFlat: chunks go straight to a file the user choseNot on Firefox, Safari or any mobile browser; the user must pick a destination first
Origin-private file system (OPFS)Chrome, Edge, Firefox, Safari (desktop and mobile)Flat while receiving: the page writes to a private, disk-backed storeThe file is invisible to the user until exported; subject to storage quota; Safari evicts it after seven days without a visit
Service-worker streamed downloadMost browsers, in principleFlatFragile: depends on the download manager behaviour of each browser and breaks on updates
In-memory Blob assemblyEverywhereGrows with the file; can briefly approach twice the size at assemblyFails unpredictably somewhere between a few hundred MB and a few GB

ShareDrop.org uses the first path where it exists, because it is the only one that writes to a location the user chose and keeps folder structure. Everywhere else it currently uses in-memory assembly with a hard cap, 512 MB per file, communicated before the transfer starts, rather than letting users discover the limit as a crash at 97%. OPFS staging would lift that cap on Firefox and Safari and is the obvious next step; its quota and eviction behaviour are why it is not there yet. The native iOS and Android apps have none of these constraints: they write to storage as data arrives.

Receiving onSaving pathMemory while receivingPractical limit
Chrome / Edge (desktop)Streamed to disk (File System Access API)Flat — a few MiB of chunk bufferDisk space
Firefox (desktop)In-memory assemblyGrows with the file512 MB, enforced before the transfer starts
Safari (macOS / iOS)In-memory assemblyGrows with the file512 MB, enforced before the transfer starts
Chrome on Android, other mobile browsersIn-memory assemblyGrows with the file512 MB, enforced before the transfer starts
ShareDrop.org iOS / Android appStreamed to storageFlatFree space on the device

What a streamed write actually does

On the streamed path the page calls showSaveFilePicker() once (the browser shows its native save dialog, the user picks a location), and receives a file handle. From that handle it opens a writable stream and writes each chunk as it arrives. Two details make this safe. The browser writes to a temporary swap file, not the final name, and only renames it into place when the stream is closed; a crash or a cancelled transfer leaves no half-written file where the user expected a complete one. And the handle is scoped to the one file the user chose: the page cannot write anywhere else, cannot read other files, and loses the permission when the tab closes. For a folder transfer the same applies to showDirectoryPicker(): one prompt, one folder, and the page may create files and subfolders inside it and nothing outside.

The origin-private file system, in more detail

OPFS is a per-site storage area on disk that every current browser provides, including Safari on iOS. A page can write to it with ordinary streams or, in a worker, with a synchronous access handle that is fast enough for real workloads. The received file could be staged there chunk by chunk, then handed to the download manager as a disk-backed File object, which does not load it into memory. Three things stand in the way of using it as the default: storage quota, which is a fraction of free disk and may be refused for a very large file; eviction, where Safari deletes a site's storage after seven days without a visit and any browser may clear it under disk pressure; and the fact that the file is invisible to the user until the final download step succeeds. Each is manageable, none is free, and the honest status is that it would raise the Safari and Firefox limit from 512 MB to something bounded by quota rather than memory.

Why 512 MB, specifically

Mobile operating systems kill a browser tab that uses too much memory, without warning and without a dialog: iOS is especially strict, and an older iPhone with several apps open can have well under a gigabyte to give a Safari tab. In-memory assembly needs the file plus working space, and the moment of assembly can briefly need more. 512 MB is a figure chosen to complete reliably on older phones, where a tab may get well under a gigabyte. It is deliberately conservative on a desktop with 32 GB of RAM, where the honest alternative is to receive in Chrome or Edge and have no cap at all.

Chunk size: smaller than you might expect

Data channel messages cannot be arbitrarily large: implementations negotiate a maximum message size, and large messages block the channel. ShareDrop.org uses 64 KiB chunks with a small binary header (transfer ID, file ID, chunk index), which keeps messages well within every browser's limits and gives fine-grained progress and incremental hashing for free. Chunking is also what makes resume-after-disconnect possible: the receiver reports the last chunk written to disk, and the sender continues from there instead of starting over.

Chunk sizeHeader overheadFits every browser's message limit?Progress granularityResent after a drop (worst case)
16 KiB~0.1%YesVery fine16 KiB
64 KiB~0.03%Yes, with marginFine — 16 updates per MiB64 KiB
256 KiB~0.01%At Chrome's limit; not safelyCoarse256 KiB
1 MiBnegligibleNoCoarse1 MiB

Below 64 KiB the per-message cost in the browser (one event, one copy) starts to show on fast links; above it, interoperability gets risky and a dropped connection wastes more. 64 KiB is the size at which none of the columns hurt.

Resume, precisely

When a connection drops mid-file, the receiver has written some number of verified chunks to disk (or to memory). On reconnect it tells the sender, per file, the index of the last chunk it has; the sender continues from the next index. Files already completed and verified are never touched again. The only data that can be resent is the tail of the file that was in flight: at most a few chunks that were sent but not yet acknowledged. A tool without chunk-level state restarts the whole file, which for a 4 GB video on a flaky connection may mean never finishing at all.

The numbers, and how they are verified

The sender side on a phone: the copy you did not ask for

Desktop browsers read a chosen file in place. Phones are different, and it matters for large videos. When you pick a video from the iOS photo library, the system does not hand the browser or app the original; it exports a copy to a temporary location first (transcoding it if the format is not one the receiver requested) and only then can the transfer read it. That export takes time proportional to the file (a minute or more for a long 4K clip, before a single byte has been sent) and needs free space equal to the file's size. A phone with 800 MB free cannot send a 1 GB video even though the video is already on it. Android provides content streams that can usually be read without a copy, so the same pick starts sending immediately. ShareDrop.org's iOS app shows the export as a preparation step rather than as a stalled transfer, and cleans the temporary copy up afterwards; a browser on iOS has less control and can only wait.

How other tools handle the same problem

Every serious transfer tool arrives at chunking and streaming, because there is no other way to move a file larger than memory; the differences are in where the chunks go.

The pattern is that streaming to disk needs a privileged writer: an operating system, a server, or a browser API that grants it for one file at a time. Browser-to-browser transfer has that on desktop Chromium and nowhere else yet, and the honest response is to say so up front.

Memory: what to expect, and how to check

The browser's own task manager (Chrome: menu → More tools → Task manager; Firefox: about:processes) shows a tab's memory live, so the difference between the two saving paths is easy to watch during a transfer. The magnitudes below are typical rather than benchmarks; the point is the shape, not the last digit.

ScenarioTab memory at startDuring a 3 GB receiveAt completion
Chrome, streamed to disk~60 MB~80–100 MB, flatSame
Chrome, sending~60 MB~70–80 MB, flat (8 MiB buffer plus overhead)Same
Firefox, in-memory (500 MB file)~90 MBRises to ~600 MBBriefly higher at assembly, then released after the download
Safari on an older iPhone (500 MB file)—Rises with the file; the system reclaims other tabs firstCompletes at 512 MB; beyond that the tab risks being reloaded, which is the reason for the cap

The two flat rows are the whole argument for streaming: memory is a property of the pipeline, not of the file. The two rising rows are the whole argument for a cap that is announced before the transfer, not discovered during it.

On the phone apps

The ShareDrop.org iOS and Android apps do not have any of these constraints, because a native app can write to storage as bytes arrive without asking a browser's permission. What they do have is the operating system's photo library: a received photo or video is written to a temporary file and then imported, and the import (which generates thumbnails and metadata) is the step that takes a few seconds at the end of a large video (what is happening at 99%). Non-media files go to the app's own folder, visible in the system Files app, with no second step.

Frequently asked

Can I really send a 50 GB file?

Yes, to a desktop receiving in Chrome or Edge, or to the mobile app, given the disk space and the time: at 25 MB/s on a LAN that is about half an hour; across the internet at 30 Mbps upload, about four hours, which is where resume after a drop stops being a nicety.

Why not raise the 512 MB cap on a desktop Safari with lots of memory?

Because the cap cannot see the memory. A page does not know whether it is running on a Mac Studio or an old iPhone, and a limit that is safe on one and fatal on the other is not a limit. The path with no cap is the one that never holds the file in memory, and on macOS that means Chrome or Edge until Safari adds a streamed save.

Does zipping a large file help?

Not for size (video, photos and archives are already compressed), and not for the cap, which applies to the zip too. Zipping helps for many files, by turning per-file overhead into one stream (folders).

Takeaways when evaluating any transfer tool

Standards referenced