Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sending File System Access API handles from WebView2 code #3706

Closed
AshleyScirra opened this issue Aug 14, 2023 · 12 comments
Closed

Sending File System Access API handles from WebView2 code #3706

AshleyScirra opened this issue Aug 14, 2023 · 12 comments
Assignees
Labels
feature request feature request tracked We are tracking this work internally.

Comments

@AshleyScirra
Copy link

AshleyScirra commented Aug 14, 2023

Problem statement

Apps like ours use web content to render the entire app, much like Electron or NW.js but using WebView2 instead. Apps like these often need to use full file system access, such as being able to write to the Documents folder or app data folder, much like a typical desktop app would. However this is not currently possible from web content.

The File System Access API is already available to web content, but it has a security and permissions model designed for web browsers, which amounts to: web content can only access files and folders that the user previously chose with a picker dialog. Things like writing directly to the app data folder is impossible. This makes it difficult to match Electron/NW.js style file system access with Node's fs APIs and so can block migrating from those tools to WebView2.

In our case we'd like web games to be able to do things like write savegame files directly to the Documents folder much like other typical PC games can. I am sure there are many other cases too - anything that you'd use the node fs APIs for in Electron/NW.js potentially applies here as well.

Describe the solution you'd like and alternatives you've considered

The solution is nearly there: the File System Access API already provides a comprehensive API for file system operations for web content, and it's already supported in WebView2. However the permissions model means that web content must show a picker dialog for the user to manually choose a file system location before gaining access to folders, which is annoying and sometimes inappropriate for things like the app data folder. Ideally there could be a minimal extension to the File System Access API to allow unrestricted use in WebView2.

In theory full file system access can be shimmed by posting messages between WebView2 and web content, but it's tricky and incompatible with existing features like the File API and File System Access API, resulting in a lot of complicated code. It seems a shame to entirely abandon the existing File System Access API, which is nearly there, and is also potentially compatible with content designed to run in browsers.

Another workaround is to abandon WebView2 and use Electron/NW.js instead. But I'd prefer to use WebView2 as it's great!

Proposed solution

I think ICoreWebView2File points the way to a solution. Currently that provides a one-way transmission of a File object in web content to be sent to WebView2 code and access the full file path. I think the best solution is for file handles to also be able to be sent from WebView2 content to web content. So for example WebView2 code could create a FileSystemDirectoryHandle for the Documents folder, post it to web content, and then web content could use that handle with permission pre-approved.

Here's pseudocode showing what I'd like to be able to do:

// In C++ WebView code

// Get string of path to local Documents folder
std::wstring documentsPath = GetDocumentsPath();

// Create a FileSystemDirectoryHandle for web content representing that folder
some_handle documentsHandle = m_webView->CreateFileSystemDirectoryHandle(documentsPath.c_str());

// Send the handle to web content
some_handle handlesArr[] = { documentsHandle };
m_webView->PostWebMessageWithAdditionalObjects("handle", handlesArr);
// In JavaScript web content code
window.chrome.webview.addEventListener("message", async e =>
{
    // Obtain FileSystemDirectoryHandle for documents folder
    const directoryHandle = e.additionalObjects[0];
    
    // Create a file in the documents folder - permission is pre-approved
    // so no prompt occurs
    const fileHandle = await directoryHandle.getFileHandle("hello.txt", { create: true });
    // ... etc ...
});

Ideally this would allow passing both FileSystemFileHandle and FileSystemDirectoryHandle, but if only one FileSystemDirectoryHandle is the more important as it allows access to entire folders rather than individual files. Ideally web content could also transmit a handle back to WebView2 content much like with ICoreWebView2File, but this is less important - our use case is mainly focused on getting WebView2 to grant expanded file system access to web content.

I don't believe this has any significant security implications: it won't grant web content access to anything the parent app didn't already have access do, or couldn't already access via WebView2 code with a more complicated messaging system.

Conclusion

I believe this represents a pretty small API surface, but the ability to create File System Access API handles in WebView2 code and post them to web content - and ideally vice-versa - would unlock full file system access for web content matching that which is already possible with frameworks like Electron and NW.js, or via the app containing WebView2. It would allow good compatibility with existing File System Access API code in web content with minimal non-standard changes, and allow convenient fallback for content in the browser by falling back to showing a picker. I'd speculate that this hopefully is also be relatively easy to implement, as presumably there is already an internal file system handle representation in the Chromium browser engine that can be created from a given path. Please consider this proposal and thanks for the great work on WebView2!

AB#45647446

@AshleyScirra AshleyScirra added the feature request feature request label Aug 14, 2023
@nishitha-burman nishitha-burman added the tracked We are tracking this work internally. label Oct 2, 2023
@aluhrs13
Copy link
Contributor

aluhrs13 commented Oct 4, 2023

@AshleyScirra - Just curious, how are you implementing your file system plugin today?

@AshleyScirra
Copy link
Author

AshleyScirra commented Oct 5, 2023

We do it via the message passing workaround. There's lots more detail in this blog post. (It also took weeks of work to set that all up.)

It's important enough that for the time being we are maintaining two separate implementations of file system access - one using the File System Access API, and one via a smaller set of operations in our File for WebView2 plugin. However obviously we'd rather only have to use a single API, and this workaround also shunts the incompatibility to our customers: they're exposed in Construct as two sets of features that don't interoperate and so the customer has to choose which file system API to use and whether to do extra work to support both. Oh, and internally it's not very efficient, as for binary files it has to pass around base64 encoded strings!

@nishitha-burman
Copy link
Collaborator

Hello,

This feature is now available as an experimental API 🎉🎉 Please give it a try and let us know if you have any feedback!

Thanks

@AshleyScirra
Copy link
Author

Very cool! I will take a look at that soon.

@AshleyScirra
Copy link
Author

@nishitha-burman - I've tried it out with the prerelease SDK using Win32/C++ but I can't figure out how to use it. I can get the interfaces and create handles, and then create a collection of handles with CreateObjectCollection(), which returns a ICoreWebView2ExperimentalObjectCollection. However PostWebMessageAsJsonWithAdditionalObjects() takes a ICoreWebView2ObjectCollectionView* argument. Notice that the latter is a collection view class, but the former is a collection class. The types do not appear to be compatible - trying to pass the object collection anyway produces a compile error cannot convert argument 2 from 'ICoreWebView2ExperimentalObjectCollection *' to 'ICoreWebView2ObjectCollectionView *'. I can't find any method that can create a collection view from a collection. Is this an omission in the SDK?

@yildirimcagri-msft
Copy link
Member

yildirimcagri-msft commented Apr 2, 2024

@nishitha-burman - I've tried it out with the prerelease SDK using Win32/C++ but I can't figure out how to use it. I can get the interfaces and create handles, and then create a collection of handles with CreateObjectCollection(), which returns a ICoreWebView2ExperimentalObjectCollection. However PostWebMessageAsJsonWithAdditionalObjects() takes a ICoreWebView2ObjectCollectionView* argument. Notice that the latter is a collection view class, but the former is a collection class. The types do not appear to be compatible - trying to pass the object collection anyway produces a compile error cannot convert argument 2 from 'ICoreWebView2ExperimentalObjectCollection *' to 'ICoreWebView2ObjectCollectionView *'. I can't find any method that can create a collection view from a collection. Is this an omission in the SDK?

Hi @AshleyScirra. You currently need to 'QueryInterface' from 'ICoreWebView2ObjectCollection*' to 'ICoreWebView2ObjectCollectionView*' to be able to pass the type to 'PostWithAdditionalObjects'. This will be fixed when the API is made stable and 'ICoreWebView2ObjectCollection' is made to derive from the 'CollectionView'. Please see our sample app 'Filesystem access scenario' for example usage. Hope this helps!

@AshleyScirra
Copy link
Author

@yildirimcagri-msft - thanks, that did the trick. That unblocks me and I'll carry on prototyping this and see how it goes.

@AshleyScirra
Copy link
Author

OK, so I was able to prototype our use case with the experimental APIs, and it seems to work great! It does exactly what we need, getting directory handles to web content and allowing file I/O without permission prompts. Thumbs up from me, and looking forwards to seeing it promoted to stable!

@AshleyScirra
Copy link
Author

May I ask what the schedule for promoting the related APIs to a (stable) release SDK is? I'd prefer to use release SDKs in production, but if that is going to be a long time I guess we could use a prerelease SDK in the interim.

@victorhuangwq
Copy link
Collaborator

https://learn.microsoft.com/en-us/microsoft-edge/webview2/release-notes/about#phases-of-adding-apis

So for this particular API, it's still an experimental API. Once we get sufficient usage / testing to determine that it's good enough to be released as a stable API, we will promote it to stable. In this step, the exact time depends on usage / testing, and on the complexity of API.

It will then be a Stable API in prerelease, before it becomes Stable API in release. Once promoted to a stable API in prerelease, it's about 1 or 2 releases before it gets into Stable SDK.

@AshleyScirra
Copy link
Author

Thanks for the information, I had missed that documentation page.

I suppose we could ship to production something built on experimental APIs in a prerelease SDK, but in my experience usually "experimental" means "don't rely on this yet". Would you recommend waiting until it's at least a stable API in a prerelease SDK instead?

@AshleyScirra
Copy link
Author

Glad to see this ship to stable in v1.0.2651.64, all looking good on my end. Thanks folks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature request feature request tracked We are tracking this work internally.
Projects
None yet
Development

No branches or pull requests

5 participants