microsoft / vscode

Visual Studio Code
https://code.visualstudio.com
MIT License
163.6k stars 29.03k forks source link

Allow for webviews to run service workers #194751

Open IanMatthewHuff opened 1 year ago

IanMatthewHuff commented 1 year ago

Our web extension is looking to allow for previewing HTML files in a VS Code webview in scenarios where the HTML file and content is hosted in local storage as opposed to hosted online. The target HTML is hosted in an iFrame inside a small wrapper main webview HTML.

To do this, we want to provide a service worker in our webview to intercept fetch calls so that our extension can handle the file requests and load and return the resources from local storage. However we've been unable to get a service worker hosted in a VS Code webview, both at the top level wrapper HTML and by trying to register it in the target hosted HTML.

When looking into service workers I believe that the following is true:

  1. Service Workers can't register with a data URL: https://github.com/w3c/ServiceWorker/issues/578 (which I believe is how web workers can work in a vscode webview)
  2. Service Workers need to match on origin: https://github.com/w3c/ServiceWorker/issues/940

So far the things that I've attempted I have all ended up essentially here:

image

I create an asWebviewUri pointing at the file of our service worker (which is added to a location accessible in localResourceRoots) but due to the origin difference the service worker can't be loaded, either here at the top level of the webview or in the iFrame hosted below.

Given what I've seen I don't think that what I'm looking to do is possible now, but if it could be enabled it would be very helpful for our scenario.

mjbvz commented 1 year ago

Have you tried using an iframe inside the webview pointing the a domain you control? That should allow service workers to be active inside of the iframe. This is essentially what we do to implement webviews

IanMatthewHuff commented 1 year ago

Have you tried using an iframe inside the webview pointing the a domain you control? That should allow service workers to be active inside of the iframe. This is essentially what we do to implement webviews

I feel I might be missing something with what you are saying about about a domain that I control. My iframe knowledge is pretty scanty before starting with this so I may just be not understanding something basic.

In the situation we're looking at (with the possibility of being an offline scenario) there really isn't another domain that any thing is coming from other than the vscode-webview:// and the resource domains created by the webview URIs. Basically seems to me that at the top level HTML hosted by the webview my domain is always going to be vscode-webview://. With the iFrame that I host below that, I could try to set src to something else. But for our scenario we are not hitting an external domain, instead we are hosting a local workspace file. In this situation I've seen that src="" here doesn't actually resolve the webview URI. For an example below: image I've stuck an image with a webview uri src just above my iframe with a webview uri src (both files are present) and while the image resolves correctly the iframe doesn't resolve the webview uri src.

As such I've had to load the iframe source in the main webview hosting HTML and then set that to the iframe's srcdoc. Now this is the part that maybe I'm off on, but with srcdoc it seems like my origin is going to be either from a special unique origin or if I specify allow-same-origin on the sandbox I can inherit the same vscode-webview://. So I'm not sure how I could point that iFrame at a different domain or what domain I would use since we just have the webview and local content available here.

Apologies if I'm way off on any of the above and thanks for the reply.

mjbvz commented 1 year ago

Maybe something like this:

  1. Inside your webview, add <iframe src="https://potentially-unique-subdomain.your-custom-domain.com"></iframe>
  2. Then at https://potentially-unique-subdomain.your-custom-domain.com, you host an html file with JS that your extension conrols
  3. This script can then register a service worker. This worker must be from a script that also lives under https://potentially-unique-subdomain.your-custom-domain.com

At this point the iframe contents are now controlled by the service worker. The iframe is also able to communicate back with its parent webview using post message

Then to load custom html inside of the iframe:

  1. Inside the iframe, use document.writeI() with the new html content
  2. This will replaces the current page contents while keeping the service worker active for the new contents

This does require hosting content at potentially-unique-subdomain.your-custom-domain.com but we also have to do this for vscode.dev too. The entire approach is actually very similar to how VS Code's webviews work. Unfortunately I don't think you can this without the external domain at this time

IanMatthewHuff commented 1 year ago

@mjbvz Thanks for the clarification. Yeah that external domain is what I thought you might have been suggesting. Right now our tool really is an online tool, but we've been trying to build around the possibility that we might deploy in learning environments (school / prisons, ect...) where adding in more domains to connect to might be restricted. So it's nice to know that with your suggestion we might have a reasonable option as long as we're ok with hosting it ourselves. We have plans to host student content for sharing at some point so we'll probably have to tackle that anyways.

One more small question, was this behavior expected?

I've stuck an image with a webview uri src just above my iframe with a webview uri src (both files are present) and while the image resolves correctly the iframe doesn't resolve the webview uri src.

It made sense to me that content hosted inside an iFrame would not be able to resolve webview URIs, but I was a bit surprised that the iFrame element itself couldn't resolve that, since I considered that element at the same level as the img.