Skip to content

Implement simple file system provider to fetch files from containerized plugin #18405

Closed
@vzhukovs

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.

didactWebView.ts#L358-L372:

		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

didactWebView.ts#L344:

		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:

didactWebView.ts#L409

			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:

webview.ts#L393:

    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:

webview.ts#L444-L486

    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:

file-service.ts#L380-L402:

    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

Metadata

Assignees

Labels

area/editor/theiaIssues related to the che-theia IDE of Chekind/taskInternal things, technical debt, and to-do tasks to be performed.severity/P1Has a major impact to usage or development of the system.

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions