Implement simple file system provider to fetch files from containerized plugin #18405
Description
Is your task related to a problem? Please describe.
Use case 1:
When VS Code extension runs in dedicate container, it can provide a Webview with own html content. For example will take a look on vscode-didact.
const completedHtml = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' data: https: http: blob: ${this._panel.webview.cspSource}; media-src vscode-resource: https: data:; script-src 'nonce-${nonce}' https:; style-src 'unsafe-inline' ${this._panel.webview.cspSource} https: data:; font-src ${this._panel.webview.cspSource} https: data:; object-src 'none';"/>
<base href="${uriBase}${uriBase.endsWith('/') ? '' : '/'}"/>
<title>Didact Tutorial</title>` +
stylesheetHtml +
`<script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
</head>
<body class="content">` + didactHtml +
`<script nonce="${nonce}" src="${scriptUri}"/>
</body>
</html>`;
src="${scriptUri}"
constructs with scheme vscode-resource
const scriptUri = scriptPathOnDisk.with({ scheme: 'vscode-resource' });
When extension calls method to create a Webview it sets up a complete html content into a webview object:
this._panel.webview.html = this.currentHtml;
So Theia receives the html content it performs preprocess income html content by replacing all links that have vscode-resource
scheme:
protected preprocessHtml(value: string): string {
return value
.replace(/(["'])(?:vscode|theia)-resource:(\/\/([^\s\/'"]+?)(?=\/))?([^\s'"]+?)(["'])/gi, (_, startQuote, _1, scheme, path, endQuote) => {
if (scheme) {
return `${startQuote}${this.externalEndpoint}/theia-resource/${scheme}${path}${endQuote}`;
}
return `${startQuote}${this.externalEndpoint}/theia-resource/file${path}${endQuote}`;
});
}
And in result Webview's html we have got links that looks like:
https://servergokcng5l-jwtproxy-webviews.192.168.64.86.nip.io/webview/theia-resource/file/tmp/vscode-unpacked/redhat.vscode-didact.0.1.14.uteaiieoau.vscode-didact-0.1.14.vsix/extension/media/webviewslim.css
were service worker extracts path: file/tmp/vscode-unpacked/redhat.vscode-didact.0.1.14.uteaiieoau.vscode-didact-0.1.14.vsix/extension/media/webviewslim.css
and tries to load such resources:
protected async loadResource(requestPath: string): Promise<void> {
const normalizedUri = this.normalizeRequestUri(requestPath);
// browser cache does not support file scheme, normalize to current endpoint scheme and host
const cacheUrl = new Endpoint({ path: normalizedUri.path.toString() }).getRestUrl().toString();
try {
if (this.contentOptions.localResourceRoots) {
for (const root of this.contentOptions.localResourceRoots) {
if (!new URI(root).path.isEqualOrParent(normalizedUri.path)) {
continue;
}
let cached = await this.resourceCache.match(cacheUrl);
try {
const result = await this.fileService.readFileStream(normalizedUri, { etag: cached?.eTag });
const { buffer } = await BinaryBufferReadableStream.toBuffer(result.value);
cached = { body: () => buffer, eTag: result.etag };
this.resourceCache.put(cacheUrl, cached);
} catch (e) {
if (!(e instanceof FileOperationError && e.fileOperationResult === FileOperationResult.FILE_NOT_MODIFIED_SINCE)) {
throw e;
}
}
if (cached) {
const data = await cached.body();
return this.doSend('did-load-resource', {
status: 200,
path: requestPath,
mime: mime.getType(normalizedUri.path.toString()) || 'application/octet-stream',
data
});
}
}
}
} catch {
// no-op
}
this.resourceCache.delete(cacheUrl);
return this.doSend('did-load-resource', {
status: 404,
path: requestPath
});
}
As the result we have got 404 error, as Theia tries to load file from local container, not from remote ones, which is obvious.
Use case 2:
From discussion with @tsmaeder it turned out similar problem, when VS Code extension that run in remote container might call: vscode.fs.watch(file, ...)
. Which will call watching for a file for a nonexistent file path.
Describe the solution you'd like
It is possible to modify schema from file
to file-sidecar-${machineName}
and get URL for links like:
https://serverh6igilxw-jwtproxy-webviews.192.168.64.88.nip.io/webview/theia-resource/file-sidecar-vscode-didact0zc/tmp/vscode-unpacked/redhat.vscode-didact.0.1.14.sdxxiurire.vscode-didact-0.1.14.vsix/extension/media/webviewslim.css
But fileService
under the hood tries to load the resource by given file system provider based on the given scheme:
async activateProvider(scheme: string): Promise<FileSystemProvider> {
let provider = this.providers.get(scheme);
if (provider) {
return provider;
}
let activation = this.activations.get(scheme);
if (!activation) {
const deferredActivation = new Deferred<FileSystemProvider>();
this.activations.set(scheme, activation = deferredActivation.promise);
WaitUntilEvent.fire(this.onWillActivateFileSystemProviderEmitter, { scheme }).then(() => {
provider = this.providers.get(scheme);
if (!provider) {
const error = new Error();
error.name = 'ENOPRO';
error.message = `No file system provider found for scheme ${scheme}`;
throw error;
} else {
deferredActivation.resolve(provider);
}
}).catch(e => deferredActivation.reject(e));
}
return activation;
}
So we are missing file system providers for schema like file-sidecar-${machineName}
which will communicate with the particular container to obtain remote files.
There is an idea to create a simple file system provider as it has been done for CheSideCarResourceResolver
: che-sidecar-resource.ts#L50-L67
Describe alternatives you've considered
- Investigate how HostedPluginReader works: plugin-reader.ts. This might help.
- Still looking for the alternative ways how to obtain files from remote container in Theia's one.
Additional context
Fixes: #16870
Might be somehow related: eclipse-theia/theia#8429 eclipse-theia/theia#8468
Does anybody has a some thoughts about this? cc @tsmaeder