Skip to content

fix(wasm): Fix saving with the download fallback and report it as supported - #25685

Open
mikernet wants to merge 4 commits into
unoplatform:masterfrom
mikernet:dev/mikernet/wasm-save-picker-supported
Open

mikernet wants to merge 4 commits into
unoplatform:masterfrom
mikernet:dev/mikernet/wasm-save-picker-supported

Conversation

@mikernet

@mikernet mikernet commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

GitHub Issue:
Fixes #24130
Fixes #25683
Fixes #25684

PR Type:

🐞 Bugfix

What changed? 🚀

Saving through FileSavePicker on WebAssembly had three problems in browsers without the File System Access API, where it falls back to the download picker:

  1. IsSavePickerSupported reported false where saving works ([WASM] False negative FileSystemAccessApiInformation.IsSavePickerSupported #24130). It only checked for showSaveFilePicker, so it was false in Brave, Firefox and Safari although the default WasmPickerConfiguration.FileSystemAccessApiWithFallback saves there with the download picker. IsOpenPickerSupported had the same problem with the upload picker.
  2. Saving failed in Safari 15.2 to 18 ([WASM] Saving through the download fallback throws TypeError: handle.createWritable is not a function on Safari 15.2 to 18 #25683). The download picker stages the file in the origin-private file system (OPFS) since fix(wasm): Stop buffering whole files in tab memory on StorageFile writes #23880, and only built the download in memory when there was no OPFS. Safari 15.2 to 18 has navigator.storage.getDirectory() but no FileSystemFileHandle.createWritable (added in Safari 26), so CachedFileManager.CompleteUpdatesAsync failed with TypeError: handle.createWritable is not a function. This is the iOS 16 failure in [WASM] False negative FileSystemAccessApiInformation.IsSavePickerSupported #24130. iOS 17 and 18 failed the same way.
  3. Saved files were padded with zeros in Safari 26 and later ([WASM] Files saved through the download fallback are padded with zeros on Safari 26+ #25684). Safari writes a view's whole underlying buffer, ignoring its byteOffset and byteLength (WebKit bug 302733). The staging wrote the last, partial 2 MB chunk as subarray(0, n), so every file was padded with zeros to a multiple of 2 MB: a 16-byte file was saved as 2,097,152 bytes, and a 4,206,649-byte file as 6,291,456 bytes.

Fixes

  • Download built in memory without createWritable: the download picker now also builds the download in memory when createWritable is missing, as it already did without OPFS. Saving therefore works in Safari 15.2 to 18, and IsSavePickerSupported is true there, like in every other browser where saving works. Reporting false there would have meant keeping saving broken on iOS 16 to 18, while the in-memory download, the path browsers without OPFS take, works there.
  • Partial last chunk copied out: the last chunk is copied before it is written. Every other chunk is its own 2 MB Uint8Array with its own buffer, so Safari writes exactly that chunk. The copy isn't limited to Safari: it is correct in every browser, costs at most one copy of the partial last chunk per file, and needs no change once WebKit fixes its bug.
  • Support reported for the fallback pickers: IsOpenPickerSupported and IsSavePickerSupported now report whether the picker can be used under the current WasmConfiguration: with the File System Access API when that is enabled and available, or with the upload or download picker when that is enabled. Each picker now chooses its implementation through one helper, which its IsSupported() uses too, so the two can't drift apart. AreFilePickersSupported follows them. IsFolderPickerSupported is unchanged; FolderPicker has no other implementation.
Browser WasmConfiguration IsSavePickerSupported before after Saving before after
Chrome, Edge default true true File System Access API File System Access API
Brave, Firefox default false true download download
Safari 26+ default false true download padded with zeros download
Safari 15.2 to 18 default false true TypeError on commit download built in memory
Any DownloadUpload File System Access API present true download download
Without the File System Access API FileSystemAccessApi false false NotSupportedException NotSupportedException

The docs section that described these properties as File System Access API checks now describes what they report; it keeps its old anchor, #checking-whether-file-system-access-api-is-supported, for existing links. It also lists the download picker's provider ID next to the other two, since StorageFile.Provider.Id is what tells a File System Access API file (jsfileaccessapi) from a download picker file (wasmdownloadpicker). The list of WasmConfiguration values, which was missing the space after each bullet and rendered as plain text, is now a list.

Tests

The new Given_FileSystemAccessApiInformation runtime tests run on Skia WebAssembly only:

  • When_Download_Upload_Pickers_Are_Enabled_Then_File_Pickers_Are_Supported: the three properties, with FileSystemAccessApiWithFallback and with DownloadUpload (6 rows).
  • When_Only_File_System_Access_Api_Is_Enabled_Then_Support_Follows_The_Browser: with FileSystemAccessApi, the properties follow showOpenFilePicker and showSaveFilePicker (2 rows).
  • When_Download_Is_Committed_Then_The_Browser_Downloads_The_Content: picks a file with the download picker, writes Hello, world!, commits it and checks the download's name and content (2 rows).
  • When_Download_Spans_Several_Chunks_Then_The_Browser_Downloads_All_Of_It: the same with 4,206,649 bytes (two full chunks and a partial one) and with exactly two chunks, checking the download's length and hash (3 rows).

The download tests record the download instead of starting it. One row of each hides createWritable, as in Safari 15.2 to 18.

Headless Chrome has the File System Access API pickers, so the support tests only fail without the fix in a browser without them. I ran them in Chrome with --disable-blink-features=FileSystemAccessLocal, which removes those pickers as Brave does, and in Safari in the iOS simulator. Results, out of 13 tests (SamplesApp browserwasm):

Browser Without the fix With the fix
Chrome (headless) 11 passed. The two downloads without createWritable fail: TypeError: handle.createWritable is not a function 13 passed
Chrome without the File System Access API pickers 5 passed. The six support rows and the two downloads without createWritable fail 13 passed
Safari 26.0.1, iOS 26.0 simulator 3 passed. The six support rows, the two downloads without createWritable (TypeError) and the two partial-chunk downloads fail (2,097,149 characters instead of 13, and 6,291,456 bytes instead of 4,206,649) 13 passed
Safari 27.0, iOS 27.0 simulator 3 passed, same failures 13 passed

All 271 Windows_Storage runtime tests pass with the fix in headless Chrome (270 passed, 1 skipped). I couldn't run Safari 15.2 to 18, since no simulator runtime for those versions is installed here, nor Firefox or Brave itself. Hiding createWritable in Safari 26 and 27 covers the same code path.

PR Checklist ✅

Behavior change: IsOpenPickerSupported, IsSavePickerSupported and AreFilePickersSupported used to report whether the File System Access API pickers are present. Apps that used them that way now get true in browsers that only have the download and upload pickers. Whether a picked file came from the File System Access API is still given by its Provider.Id. With the FileSystemAccessApi configuration, the properties still report the File System Access API pickers.

Copilot AI balanced review requested due to automatic review settings October 25, 2026 08:04
@github-actions github-actions Bot added platform/wasm 🌐 Categorizes an issue or PR as relevant to the WebAssembly platform kind/documentation labels Oct 25, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The localized fixes preserve picker selection, address the reported failures, and include targeted regression coverage with no identified blocking issues.

No open issues 🎉

What changed in this PR

Fixes WebAssembly picker support reporting and Safari download failures for #24130, #25683, and #25684, following the chunked-storage changes in #23880.

Changes:

  • Aligns file-picker support checks with the implementation selected by WasmConfiguration.
  • Uses in-memory downloads when writable storage is unavailable and copies partial chunks to prevent Safari zero-padding.
  • Adds regression tests and updates capability and provider documentation.
File Description
src/​Uno.WinRT/​ts/​Windows/​Storage/​Streams/​NativeChunkedBuffer.ts Fixes writable-storage detection and partial-chunk downloads.
src/​Uno.WinRT/​Storage/​Pickers/​FileSavePicker.wasm.cs Shares implementation selection with save support checks.
src/​Uno.WinRT/​Storage/​Pickers/​FileOpenPicker.wasm.cs Shares implementation selection with open support checks.
src/​Uno.UI.RuntimeTests/​Windows/​Storage/​Pickers/​Given_FileSystemAccessApiInformation.cs Tests picker support and downloaded content integrity.
src/​Uno.UI.Runtime.WebAssembly.Browser/​Storage/​Pickers/​FileSystemAccessApiInformation.cs Reports support for configured native or fallback file pickers.
doc/​articles/​features/​windows-storage-pickers.md Clarifies support semantics, provider IDs, and configuration options.

Review effort: Balanced

@unodevops

Copy link
Copy Markdown
Contributor

🤖 Your Docs stage site is ready! Visit it here: https://unodocsprstaging.z13.web.core.windows.net/pr-25685/docs/index.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind/documentation platform/wasm 🌐 Categorizes an issue or PR as relevant to the WebAssembly platform

Projects

None yet

3 participants