Downloads and Network Control
Save downloaded files and inspect, filter, or secure network requests from a View.
On this page
You can attach listeners to a View so you can save files downloaded by the page and allow, block, or secure outgoing network requests— the renderer does neither on its own.
Saving Downloads
When a page starts a file download, you receive the incoming data and write it to disk.
Attaching a Download Listener
To handle downloads, implement DownloadListener and attach it with View::set_download_listener(). Without a listener attached, the View ignores all downloads.
The View stores only a pointer to the listener without taking ownership— you must keep the listener alive for as long as the View uses it (see Handling View Events).
DownloadListener has no default implementations— you must implement all six callbacks. The renderer calls each callback on the Renderer's thread (the thread that created the Renderer).
When Downloads Start
A download starts when the page navigates to a file the renderer can't display.
This happens when the server sends a Content-Disposition: attachment header, or returns a MIME type the library can't render.
The renderer doesn't support the HTML download attribute on links— clicking one navigates to the file like an ordinary link.
Handling Callbacks in Order
The renderer calls the download callbacks in order throughout the transfer.
| Callback | Action |
|---|---|
NextDownloadId() |
Return a unique ID on each call (an incrementing counter starting at 0 works well). |
OnRequestDownload() |
Return true to allow the download or false to block it. When blocked, no further callbacks fire for that ID. |
OnBeginDownload() |
Open a destination file on disk. The callback receives the URL, a suggested filename, and the expected file size in bytes. |
OnReceiveDataForDownload() |
Write incoming chunks to the file. The renderer calls this repeatedly as data arrives. |
OnFinishDownload() |
Close the completed file. |
OnFailDownload() |
Close the file and delete the partial download from disk. |
🚧 Do Not Keep the Buffer
The
Bufferpassed toOnReceiveDataForDownload()is valid only during the call. Write the bytes to disk or copy them inside the callback— never keep theBufferafter the callback returns.
Saving Downloads to a Folder
The following listener tracks active downloads by ID and saves incoming files into a downloads folder:
#include <Ultralight/Ultralight.h>
#include <cstdio>
#include <fstream>
#include <map>
#include <string>
using namespace ultralight;
RefPtr<View> view;
class MyDownloadListener : public DownloadListener {
public:
///
/// Hand out ids in order, starting at 0.
///
DownloadId NextDownloadId(View* caller) override { return next_id_++; }
///
/// Allow every download (return false here to block one).
///
bool OnRequestDownload(View* caller, DownloadId id,
const String& url) override {
return true;
}
///
/// The View writes nothing to disk, so open a file for this download
/// ourselves. The suggested name can be empty, so fall back to the id.
///
void OnBeginDownload(View* caller, DownloadId id, const String& url,
const String& filename,
int64_t expected_content_length) override {
std::string name = filename.utf8().data();
if (name.empty())
name = "download-" + std::to_string(id);
Download& download = downloads_[id];
download.path = "downloads/" + name;
download.file.open(download.path, std::ios::binary);
}
///
/// Data streams in over many calls, so append each chunk to the file.
///
void OnReceiveDataForDownload(View* caller, DownloadId id,
RefPtr<Buffer> data) override {
downloads_[id].file.write(static_cast<const char*>(data->data()),
data->size());
}
///
/// Close the file when the download completes.
///
void OnFinishDownload(View* caller, DownloadId id) override {
downloads_[id].file.close();
downloads_.erase(id);
}
///
/// On failure, close the file and delete the partial download.
/// (This can fire before OnBeginDownload, so look the id up first.)
///
void OnFailDownload(View* caller, DownloadId id) override {
auto it = downloads_.find(id);
if (it == downloads_.end())
return;
it->second.file.close();
std::remove(it->second.path.c_str());
downloads_.erase(it);
}
private:
struct Download {
std::string path;
std::ofstream file;
};
DownloadId next_id_ = 0;
std::map<DownloadId, Download> downloads_;
};
MyDownloadListener downloads;
void AttachDownloadListener() {
///
/// Hand the View a pointer to our listener (ownership stays with us).
///
view->set_download_listener(&downloads);
}
Handling the Suggested Filename
The suggested filename passed to OnBeginDownload() contains only the file name itself— the library strips all directory paths from the server response and the URL.
Because the suggested name can be empty, you should provide a fallback name when opening the destination file.
Handling Download Failures
The renderer can call OnFailDownload() for a download that never reached OnBeginDownload() (eg, if the network connection drops before the server responds).
You should verify that the download ID exists before attempting to close or remove a file— if OnBeginDownload() never ran, no destination file was created on disk.
Canceling a Download
To stop an active download, call View::CancelDownload() with its ID.
The renderer stops sending data for that download and calls OnFailDownload() during a later Renderer::Update(). You can clean up and delete the partial file in that callback just as you would for any other failure.
🚧 Clean Up Open Files When Releasing a View
Releasing a View ends all of its active downloads without firing any callbacks— not even
OnFailDownload(). You must close and delete any files that are still open when releasing the View.
Filtering Network Requests
You can intercept and filter the HTTP and HTTPS requests a page sends before they reach the network.
📘 Free Edition Limitations
The Free edition does not include a network filter, and
OnNetworkRequest()is never called. Do not rely on network listeners to enforce security in builds compiled with the Free edition (see Editions and Feature Macros).
Attaching a Network Listener
To inspect and filter network requests, implement NetworkListener and attach it with View::set_network_listener().
The View stores only a pointer to the listener without taking ownership— you must keep the listener alive for as long as the View uses it.
The renderer calls OnNetworkRequest() on the Renderer's thread (the thread that created the Renderer) before sending each request. The callback receives every HTTP and HTTPS request the page makes, including documents, stylesheets, scripts, images, fetch() calls, and asynchronous XMLHttpRequests. Requests for file: and data: URLs bypass the listener, as do synchronous XMLHttpRequests.
Allowing or Blocking Requests
Return true from OnNetworkRequest() to allow the request to proceed. Return false to block the request, which causes the resource load to fail immediately on the page.
The callback receives a NetworkRequest& parameter describing the outgoing request. You can inspect request.url(), request.urlHost(), the HTTP method, the request origin, and the user agent (see the NetworkRequest reference).
Public Key Pinning
You can enforce additional TLS validation on outgoing requests by pinning the server's public key— if the server's key does not match, the connection fails.
Call NetworkRequest::EnforcePinnedPublicKey() with one or more base64-encoded SHA-256 hashes. Prefix each hash with sha256// and separate multiple hashes with semicolons (the format used by cURL's CURLOPT_PINNEDPUBLICKEY).
The following example restricts outgoing requests to a specific host and pins its public key:
#include <Ultralight/Ultralight.h>
using namespace ultralight;
RefPtr<View> view;
class MyNetworkListener : public NetworkListener {
public:
///
/// Let requests to our own host through and block everything else.
///
bool OnNetworkRequest(View* caller, NetworkRequest& request) override {
if (request.urlHost() != "ultralig.ht")
return false;
///
/// Pin our server's public key (additional TLS certificate validation).
///
request.EnforcePinnedPublicKey(
"sha256//YhKJKSzoTt2b5FP18fvpHo7fJYqQCjAa3HWY3tvRMwE=");
return true;
}
};
MyNetworkListener network;
void AttachNetworkListener() {
///
/// Hand the View a pointer to our listener (ownership stays with us).
///
view->set_network_listener(&network);
}