未验证 提交 cf0094f9 编写于 作者: M Matt Bierner 提交者: GitHub

Add delegation back to renderer process for cetain webview resource reeds (#102442)

Fixes #102191

**Problem**
Webviews used to load local resources in the renderer process. This gave them access to all of the file system providers registered for that renderer (such as `untitled` and `git`).

However, last iteration we moved the webview protocol to the main process. This works fine for file resources, but the main process cannot read fancy uris such as `git:` or `untitled:`

**Fix**
To fix this, I've added code that delegates resource requests for non-file uris from the main process back to the renderer process. We tried to avoid this as it adds another round trip to each request, but I don't see a better way of doing this.

This problem only effects desktop VS Code. Web VS Code uses a service worker which always reads resources through the renderer process
上级 a7285b47
......@@ -9,7 +9,6 @@ import { isUNC } from 'vs/base/common/extpath';
import { Schemas } from 'vs/base/common/network';
import { sep } from 'vs/base/common/path';
import { URI } from 'vs/base/common/uri';
import { IFileService } from 'vs/platform/files/common/files';
import { IRemoteConnectionData } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IRequestService } from 'vs/platform/request/common/request';
import { getWebviewContentMimeType } from 'vs/platform/webview/common/mimeTypes';
......@@ -35,6 +34,10 @@ export namespace WebviewResourceResponse {
export type StreamResponse = StreamSuccess | typeof Failed | typeof AccessDenied;
}
interface FileReader {
readFileStream(resource: URI): Promise<VSBufferReadableStream>;
}
export async function loadLocalResource(
requestUri: URI,
options: {
......@@ -43,7 +46,7 @@ export async function loadLocalResource(
remoteConnectionData?: IRemoteConnectionData | null;
rewriteUri?: (uri: URI) => URI,
},
fileService: IFileService,
fileReader: FileReader,
requestService: IRequestService,
): Promise<WebviewResourceResponse.StreamResponse> {
let resourceToLoad = getResourceToLoad(requestUri, options.roots);
......@@ -67,8 +70,8 @@ export async function loadLocalResource(
}
try {
const contents = await fileService.readFileStream(resourceToLoad);
return new WebviewResourceResponse.StreamSuccess(contents.value, mime);
const contents = await fileReader.readFileStream(resourceToLoad);
return new WebviewResourceResponse.StreamSuccess(contents, mime);
} catch (err) {
console.log(err);
return WebviewResourceResponse.Failed;
......@@ -96,8 +99,14 @@ function normalizeRequestPath(requestUri: URI) {
//
// vscode-webview-resource://id/scheme//authority?/path
//
const resourceUri = URI.parse(requestUri.path.replace(/^\/([a-z0-9\-]+)\/{1,2}/i, '$1://'));
const resourceUri = URI.parse(requestUri.path.replace(/^\/([a-z0-9\-]+)(\/{1,2})/i, (_: string, scheme: string, sep: string) => {
if (sep.length === 1) {
return `${scheme}:///`; // Add empty authority.
} else {
return `${scheme}://`; // Url has own authority.
}
}));
console.log(requestUri, resourceUri);
return resourceUri.with({
query: requestUri.query,
fragment: requestUri.fragment
......
......@@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { VSBuffer } from 'vs/base/common/buffer';
import { UriComponents } from 'vs/base/common/uri';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IRemoteConnectionData } from 'vs/platform/remote/common/remoteAuthorityResolver';
......@@ -13,10 +14,12 @@ export const IWebviewManagerService = createDecorator<IWebviewManagerService>('w
export interface IWebviewManagerService {
_serviceBrand: unknown;
registerWebview(id: string, webContentsId: number | undefined, metadata: RegisterWebviewMetadata): Promise<void>;
registerWebview(id: string, webContentsId: number | undefined, windowId: number, metadata: RegisterWebviewMetadata): Promise<void>;
unregisterWebview(id: string): Promise<void>;
updateWebviewMetadata(id: string, metadataDelta: Partial<RegisterWebviewMetadata>): Promise<void>;
didLoadResource(requestId: number, content: VSBuffer | undefined): void;
setIgnoreMenuShortcuts(webContentsId: number, enabled: boolean): Promise<void>;
}
......
......@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { webContents } from 'electron';
import { VSBuffer } from 'vs/base/common/buffer';
import { Disposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { IFileService } from 'vs/platform/files/common/files';
......@@ -12,6 +13,7 @@ import { IRequestService } from 'vs/platform/request/common/request';
import { IWebviewManagerService, RegisterWebviewMetadata } from 'vs/platform/webview/common/webviewManagerService';
import { WebviewPortMappingProvider } from 'vs/platform/webview/electron-main/webviewPortMappingProvider';
import { WebviewProtocolProvider } from 'vs/platform/webview/electron-main/webviewProtocolProvider';
import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
export class WebviewMainService extends Disposable implements IWebviewManagerService {
......@@ -24,17 +26,19 @@ export class WebviewMainService extends Disposable implements IWebviewManagerSer
@IFileService fileService: IFileService,
@IRequestService requestService: IRequestService,
@ITunnelService tunnelService: ITunnelService,
@IWindowsMainService windowsMainService: IWindowsMainService,
) {
super();
this.protocolProvider = this._register(new WebviewProtocolProvider(fileService, requestService));
this.protocolProvider = this._register(new WebviewProtocolProvider(fileService, requestService, windowsMainService));
this.portMappingProvider = this._register(new WebviewPortMappingProvider(tunnelService));
}
public async registerWebview(id: string, webContentsId: number | undefined, metadata: RegisterWebviewMetadata): Promise<void> {
public async registerWebview(id: string, webContentsId: number | undefined, windowId: number, metadata: RegisterWebviewMetadata): Promise<void> {
const extensionLocation = metadata.extensionLocation ? URI.from(metadata.extensionLocation) : undefined;
this.protocolProvider.registerWebview(id, {
...metadata,
windowId: windowId,
extensionLocation,
localResourceRoots: metadata.localResourceRoots.map(x => URI.from(x))
});
......@@ -75,4 +79,8 @@ export class WebviewMainService extends Disposable implements IWebviewManagerSer
contents.setIgnoreMenuShortcuts(enabled);
}
}
public async didLoadResource(requestId: number, content: VSBuffer | undefined): Promise<void> {
this.protocolProvider.didLoadResource(requestId, content);
}
}
......@@ -3,19 +3,21 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { session, protocol } from 'electron';
import { protocol, session } from 'electron';
import { Readable } from 'stream';
import { VSBufferReadableStream } from 'vs/base/common/buffer';
import { bufferToStream, VSBuffer, VSBufferReadableStream } from 'vs/base/common/buffer';
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import { URI } from 'vs/base/common/uri';
import { IFileService } from 'vs/platform/files/common/files';
import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files';
import { IRemoteConnectionData } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { IRequestService } from 'vs/platform/request/common/request';
import { loadLocalResource, webviewPartitionId, WebviewResourceResponse } from 'vs/platform/webview/common/resourceLoader';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
interface WebviewMetadata {
readonly windowId: number;
readonly extensionLocation: URI | undefined;
readonly localResourceRoots: readonly URI[];
readonly remoteConnectionData: IRemoteConnectionData | null;
......@@ -32,9 +34,13 @@ export class WebviewProtocolProvider extends Disposable {
private readonly webviewMetadata = new Map<string, WebviewMetadata>();
private requestIdPool = 1;
private readonly pendingResourceReads = new Map<number, { resolve: (content: VSBuffer | undefined) => void }>();
constructor(
@IFileService private readonly fileService: IFileService,
@IRequestService private readonly requestService: IRequestService,
@IWindowsMainService readonly windowsMainService: IWindowsMainService,
) {
super();
......@@ -166,12 +172,42 @@ export class WebviewProtocolProvider extends Disposable {
};
}
const fileService = {
readFileStream: async (resource: URI): Promise<VSBufferReadableStream> => {
if (uri.scheme === Schemas.file) {
return (await this.fileService.readFileStream(resource)).value;
}
// Unknown uri scheme. Try delegating the file read back to the renderer
// process which should have a file system provider registered for the uri.
const window = this.windowsMainService.getWindowById(metadata.windowId);
if (!window) {
throw new FileOperationError('Could not find window for resource', FileOperationResult.FILE_NOT_FOUND);
}
const requestId = this.requestIdPool++;
const p = new Promise<VSBuffer | undefined>(resolve => {
this.pendingResourceReads.set(requestId, { resolve });
});
window.send(`vscode:loadWebviewResource-${id}`, requestId, uri);
const result = await p;
if (!result) {
throw new FileOperationError('Could not read file', FileOperationResult.FILE_NOT_FOUND);
}
return bufferToStream(result);
}
};
const result = await loadLocalResource(uri, {
extensionLocation: metadata.extensionLocation,
roots: metadata.localResourceRoots,
remoteConnectionData: metadata.remoteConnectionData,
rewriteUri,
}, this.fileService, this.requestService);
}, fileService, this.requestService);
if (result.type === WebviewResourceResponse.Type.Success) {
return callback({
......@@ -195,4 +231,13 @@ export class WebviewProtocolProvider extends Disposable {
return callback({ data: null, statusCode: 404 });
}
public didLoadResource(requestId: number, content: VSBuffer | undefined) {
const pendingRead = this.pendingResourceReads.get(requestId);
if (!pendingRead) {
throw new Error('Unknown request');
}
this.pendingResourceReads.delete(requestId);
pendingRead.resolve(content);
}
}
......@@ -167,7 +167,9 @@ export class IFrameWebview extends BaseWebview<HTMLIFrameElement> implements Web
roots: this.content.options.localResourceRoots || [],
remoteConnectionData,
rewriteUri,
}, this.fileService, this.requestService);
}, {
readFileStream: (resource) => this.fileService.readFileStream(resource).then(x => x.value),
}, this.requestService);
if (result.type === WebviewResourceResponse.Type.Success) {
const { buffer } = await streamToBuffer(result.stream);
......
......@@ -4,17 +4,23 @@
*--------------------------------------------------------------------------------------------*/
import { equals } from 'vs/base/common/arrays';
import { streamToBuffer } from 'vs/base/common/buffer';
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
import { URI, UriComponents } from 'vs/base/common/uri';
import { createChannelSender } from 'vs/base/parts/ipc/common/ipc';
import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals';
import * as modes from 'vs/editor/common/modes';
import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron';
import { IFileService } from 'vs/platform/files/common/files';
import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
import { ILogService } from 'vs/platform/log/common/log';
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IRequestService } from 'vs/platform/request/common/request';
import { loadLocalResource, WebviewResourceResponse } from 'vs/platform/webview/common/resourceLoader';
import { IWebviewManagerService } from 'vs/platform/webview/common/webviewManagerService';
import { WebviewContentOptions, WebviewExtensionDescription } from 'vs/workbench/contrib/webview/browser/webview';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { Schemas } from 'vs/base/common/network';
/**
* Try to rewrite `vscode-resource:` urls in html
......@@ -57,6 +63,9 @@ export class WebviewResourceRequestManager extends Disposable {
@IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService,
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
@IMainProcessService mainProcessService: IMainProcessService,
@IElectronService electronService: IElectronService,
@IFileService fileService: IFileService,
@IRequestService requestService: IRequestService,
) {
super();
......@@ -72,8 +81,7 @@ export class WebviewResourceRequestManager extends Disposable {
this._ready = getWebContentsId.then(async (webContentsId) => {
this._logService.debug(`WebviewResourceRequestManager(${this.id}): did-start-loading`);
await this._webviewManagerService.registerWebview(this.id, webContentsId, {
await this._webviewManagerService.registerWebview(this.id, webContentsId, electronService.windowId, {
extensionLocation: this.extension?.location.toJSON(),
localResourceRoots: this._localResourceRoots.map(x => x.toJSON()),
remoteConnectionData: remoteConnectionData,
......@@ -93,6 +101,30 @@ export class WebviewResourceRequestManager extends Disposable {
}
this._register(toDisposable(() => this._webviewManagerService.unregisterWebview(this.id)));
const loadResourceChannel = `vscode:loadWebviewResource-${id}`;
const loadResourceListener = async (_event: any, requestId: number, resource: UriComponents) => {
try {
const response = await loadLocalResource(URI.revive(resource), {
extensionLocation: this.extension?.location,
roots: this._localResourceRoots,
remoteConnectionData: remoteConnectionData,
}, {
readFileStream: (resource) => fileService.readFileStream(resource).then(x => x.value),
}, requestService);
if (response.type === WebviewResourceResponse.Type.Success) {
const buffer = await streamToBuffer(response.stream);
return this._webviewManagerService.didLoadResource(requestId, buffer);
}
} catch {
// Noop
}
this._webviewManagerService.didLoadResource(requestId, undefined);
};
ipcRenderer.on(loadResourceChannel, loadResourceListener);
this._register(toDisposable(() => ipcRenderer.removeListener(loadResourceChannel, loadResourceListener)));
}
public update(options: WebviewContentOptions) {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册