r-wasm / webr

The statistical language R compiled to WebAssembly via Emscripten, for use in web browsers and Node.
https://docs.r-wasm.org/webr/latest/
Other
878 stars 69 forks source link

Thoughts on shimming browseURL() #295

Open ColinFay opened 1 year ago

ColinFay commented 1 year ago

The browseURL() function defines :

url: a non-empty character string giving the URL to be loaded. Some platforms also accept file paths.

As far as I can tell, this can be shimmed via running JS code in the main thread.

# in webr
webr::browseURL(url)

Two case:

windows.open(url);
# write file from the R instance 
await webR.evalR('write("<h1>Hello world</h1>", "hello.html")');
const html = await webR.FS.readFile("/home/web_user/hello.html");
const html_content = new TextDecoder().decode(html);
var newWindow = window.open();
newWindow.document.write(html_content);

Possibly, we might start with supporting only remote URL (the helps says "Some platforms also accept file paths.", so I feel like this is ok to not support local file in a first time).

I'm not exactly sure how to send JS code from webr to be executed in the main thread, though, any help on how to do this would be awesome :)

Thanks

georgestagg commented 1 year ago

Yes, we will definitely want to handle utils::browseURL() at some point. I am unsure as of yet whether it's better to handle this through patching R's source or shimming using the webr package. If the implementation becomes complex, with several moving parts, it might be more elegant to patch R itself directly.

One issue I can think of is that if the local HTML file depends on other files on the VFS (e.g. scripts or images), those too would need to be transferred to the main thread for display. It would probably be possible to parse the HTML file in question to look for dependencies, but that might not be the best solution.

Making use of a JS Service Worker might be possible and prefereable. This is how Shinylive exposes a web server in the R process to the main thread. However, only one service worker can be active at a time for a particular origin, so such a method will need to be mindful of the existing webR Service Worker communication channel that may or may not already be running in the page.


I'm not exactly sure how to send JS code from webr to be executed in the main thread

The easiest way is probably through a custom webR channel message. Running the following in R:

webr::eval_js("chan.write({ type: 'myEvent', data: { mydata: 123 } })")

will cause a custom message to appear in webR.flush()/webR.read() on the main thread. This can be used to pass messages and information from the R process to be handled in the main thread JavaScript thread. This mechanism is how documentation display is handled, for example, in: https://github.com/r-wasm/webr/blob/1c36fda2314dc950ba30399dff4d9861c122d680/packages/webr/R/pager.R#L14-L27.

seanbirchall commented 10 months ago

+1 this would be super useful!

georgestagg commented 4 months ago

With the infrastructure added by #449 the steps to implement this are now much more approachable:

1) Create a new version of openHtmlInEditor() that takes a URL rather than the HTML source.

2) Edit handleBrowseMessage() so as to handle URLs beginning with "http[s]://" with the new function in 1).

3) Shim browseURL() similar to the other functions in shims.R.