fix(web): refuse a host file of exactly 2 GiB

wasm32 cannot allocate a Vec of 2 GiB (isize::MAX is one byte less), so
the open check that only rejected a larger file trapped on that boundary.
Refuse 2 GiB as well, and say so in the failure the host receives.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
FredAppsandCursor committed 2026-11-05 13:59:10 +01:00
1 parent bfdcc5cdfa
commit a2c1633fe3
2 files changed
+8 -6

No files matched your search

+7 -5
View File
@@ -27,7 +27,7 @@
//! - Export (PDF, pictures, outline, GIF, handouts, a Save As type that isn't a presentation)
//! and Print: `false`. They are copies; the presentation's file stays the same.
//! - `deckcraft:dirty` whenever unsaved changes appear or go away, `deckcraft:failed` when the
//! app couldn't start or refused a file over 2 GiB.
//! app couldn't start or refused a file of 2 GiB or larger.
//!
//! In host mode the app starts with an empty presentation instead of the sample, which gives way to
//! the first file the host sends; the host guards leaving the page, and `?author=` names the user
@@ -46,7 +46,9 @@ const APP: &str = "deckcraft";
/// falls back to this): no name is no file, so the presentation opens untitled.
const NAMELESS: &str = "document";
/// The largest file a host may send, the limit File › Open has on the desktop (2 GiB): a bigger one is refused before it is copied.
/// The largest file a host may send. File › Open on the desktop refuses anything over 2 GiB.
/// On wasm32 a `Vec` cannot hold 2 GiB (`isize::MAX` is one byte less), so a file of exactly
/// 2 GiB is refused too, before its bytes are copied.
const MAX_OPEN_BYTES: f64 = 2_147_483_648.0;
/// The longest user name taken from `?author=` (the preferences' own limit).
@@ -129,9 +131,9 @@ impl Bridge {
let host = self.host.clone();
self.host.listen(move |message| {
let Message::Open { name, data } = message else { return };
if data.size() > MAX_OPEN_BYTES {
log::warn!("host file {name} is over 2 GiB; not opened");
let _ = host.post_failed(&format!("{name} is larger than 2 GiB, the largest file DeckCraft opens"), None);
if data.size() >= MAX_OPEN_BYTES {
log::warn!("host file {name} is 2 GiB or larger; not opened");
let _ = host.post_failed(&format!("{name} is 2 GiB or larger, which DeckCraft's web build cannot open"), None);
return;
}
let FileData::Bytes(array) = data else {
+1 -1
View File
@@ -90,7 +90,7 @@ Messages are plain objects with a `type`:
| `deckcraft:open` | from the page | `name` (a bare file name, such as `Talk.pptx`), `bytes` (`ArrayBuffer` or `Uint8Array`). Opens like File › Open: a presentation (.pptx, .potx, .ppsx, .deckcraft) has that name as its file; an outline (.txt, .md, .rtf), a file sent without a name and a damaged file open untitled. |
| `deckcraft:save` | to the page | `name`, `saveAs`, `bytes` (a transferred `ArrayBuffer`), for every file DeckCraft would otherwise download. The page stores it. |
| `deckcraft:dirty` | to the page | `dirty`, whenever unsaved changes appear or go away. |
| `deckcraft:failed` | to the page | `error`, when the app couldn't start (no WebGPU or WebGL2) or refused a file over 2 GiB. |
| `deckcraft:failed` | to the page | `error`, when the app couldn't start (no WebGPU or WebGL2) or refused a file of 2 GiB or larger. |
`saveAs` says whether the file becomes the presentation's file, as on the desktop: