How Safari Handles Dynamic Blob and Data URL Downloads
This article provides an overview of how the Safari web browser
processes and manages file downloads initiated dynamically through
client-side JavaScript using blob: and data:
URLs. It covers how Safari's WebKit engine interacts with the HTML5
download attribute, the specific behaviors and limitations
of both URL schemes, security constraints like user gesture
requirements, and the practical workarounds developers use to ensure
reliable file downloads across macOS and iOS devices.
The Role of the HTML5 Download Attribute
To trigger a client-side download in modern browsers, developers
dynamically create an HTML anchor (<a>) element,
assign the blob: or data: URL to its
href attribute, set the download attribute to
the desired filename, and programmatically trigger a click event.
Safari supports the download attribute starting from
Safari 10.1 on macOS and iOS 13 on iOS/iPadOS. Without this attribute,
Safari attempts to navigate to the URL and render the content inline
rather than downloading it as a standalone file.
Handling blob: URLs
A blob: URL (Binary Large Object) is generated in memory
using the URL.createObjectURL() method. It is the preferred
method for downloading larger files generated on the client side. Safari
handles blob: URLs with a few specific caveats:
- MIME Type Sensitivity: Safari relies heavily on the
Blobobject's MIME type. If you specify a known MIME type that Safari can render natively (such asapplication/pdf,image/png, ortext/plain), Safari may occasionally ignore thedownloadattribute and open the file in the current tab—especially if the download is not triggered correctly. Setting the MIME type toapplication/octet-streamforces Safari to treat the blob as a generic binary file, ensuring a download occurs, though it may sometimes cause Safari to append an incorrect file extension if thedownloadattribute is malformed. - Premature URL Revocation: To free up memory,
developers call
URL.revokeObjectURL()after triggering the download. In Chrome and Firefox, this can be done immediately after the programmatic.click()call. In Safari, revoking the URL immediately causes the download to fail silently because WebKit handles the download asynchronously. Developers must wrapURL.revokeObjectURL()in asetTimeout(typically 100ms to 1000ms, or up to 40 seconds for very large files) to give Safari enough time to initiate the file stream. - Cross-Origin Restrictions:
blob:URLs are bound to the origin of the document that created them. Safari strictly enforces same-origin policies, meaning ablob:URL generated in aniframefrom a different domain cannot be downloaded using thedownloadattribute in the parent frame.
Handling data: URLs
A data: URL embeds the file content directly into the
URL string, usually encoded in Base64. While useful for small files
(like small SVGs, JSON snippets, or canvas exports), Safari enforces
strict security and performance limits on them:
- Top-Level Navigation Blocking: As a security
measure against phishing, WebKit blocks top-level navigation to
data:URLs. If a script attempts to trigger a download by callingwindow.open('data:...')or settingwindow.location.href = 'data:...', Safari will block the action and log a security error in the console. - Mandatory Anchor Tag Usage: Because top-level
navigation is blocked,
data:URLs must be downloaded using an<a download="filename.ext">element. When combined with thedownloadattribute, Safari bypasses the navigation block and routes the data stream directly to the browser's download manager. - Size Limitations: Because
data:URLs store the entire file as a string in memory, they consume significantly more RAM than blobs. Safari on iOS has strict memory limits for web views; attempting to generate and download adata:URL larger than a few megabytes can crash the browser tab.
The User Gesture Requirement
Safari features aggressive popup and download blockers to protect
user experience. A dynamic download using a blob: or
data: URL will only succeed if it is triggered by a
"trusted" user gesture, such as a direct mouse click or tap event.
If your JavaScript application needs to fetch data from an API or
perform a lengthy asynchronous task (like generating a complex PDF)
before creating the blob: or data: URL, the
trusted event context expires while waiting for the Promise to resolve.
When the programmatic anchor.click() finally executes,
Safari will block the download.
To bypass this limitation in Safari, developers must separate the
asynchronous generation step from the download step. For example, a user
clicks a "Generate File" button (which prepares the blob
asynchronously), and once ready, the UI displays a "Download File"
button. Clicking this second button executes the blob:
download synchronously within a fresh user gesture context.
Differences Between macOS and iOS Safari
While both platforms use WebKit, the user experience differs slightly:
- macOS Safari: Downloads initiated via
blob:ordata:URLs go straight to the user's designated "Downloads" folder, flying into the download manager icon in the browser toolbar without interrupting the user, provided the site has permission to download files. - iOS Safari: When a
blob:ordata:download is triggered on an iPhone or iPad, iOS Safari always interrupts the user with a system modal asking, "Do you want to download '[filename]'?" The user must manually tap "Download" for the file to be saved to the iOS Files app. If theblob:URL was revoked before the user taps this confirmation button, the download will fail. Therefore, on iOS, it is often safer not to revoke theblob:URL immediately, or to delay revocation for at least 30 to 60 seconds.