Skip to content

Commit

Permalink
Move webview protocol back to using main process
Browse files Browse the repository at this point in the history
Fixes #89038
For #95955

This moves the webview local resource protocol back to the main process. It should now also handle remote cases by sending the remote data back to the main process
  • Loading branch information
mjbvz committed Jun 15, 2020
1 parent 0ed1be8 commit d2ae606
Show file tree
Hide file tree
Showing 8 changed files with 120 additions and 121 deletions.
2 changes: 0 additions & 2 deletions src/vs/base/common/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,6 @@ export namespace Schemas {

export const webviewPanel = 'webview-panel';

export const oldVscodeWebviewResource = 'vscode-resource';

export const vscodeWebviewResource = 'vscode-webview-resource';

/**
Expand Down
59 changes: 28 additions & 31 deletions src/vs/platform/webview/common/resourceLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@
*--------------------------------------------------------------------------------------------*/

import { VSBuffer, VSBufferReadableStream } from 'vs/base/common/buffer';
import { CancellationToken } from 'vs/base/common/cancellation';
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 { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { IRequestService } from 'vs/platform/request/common/request';
import { getWebviewContentMimeType } from 'vs/platform/webview/common/mimeTypes';

export namespace WebviewResourceResponse {
Expand Down Expand Up @@ -63,18 +66,38 @@ export async function loadLocalResource(

export async function loadLocalResourceStream(
requestUri: URI,
options: {
extensionLocation: URI | undefined;
roots: ReadonlyArray<URI>;
remoteConnectionData?: IRemoteConnectionData | null;
},
fileService: IFileService,
extensionLocation: URI | undefined,
roots: ReadonlyArray<URI>
requestService: IRequestService,
): Promise<WebviewResourceResponse.StreamResponse> {
const resourceToLoad = getResourceToLoad(requestUri, extensionLocation, roots);
const resourceToLoad = getResourceToLoad(requestUri, options.extensionLocation, options.roots);
if (!resourceToLoad) {
return WebviewResourceResponse.AccessDenied;
}
const mime = getWebviewContentMimeType(requestUri); // Use the original path for the mime

if (options.remoteConnectionData) {
// Remote uris must go to the resolved server.
if (resourceToLoad.scheme === Schemas.vscodeRemote || (options.extensionLocation?.scheme === REMOTE_HOST_SCHEME)) {
const uri = URI.parse(`http://${options.remoteConnectionData.host}:${options.remoteConnectionData.port}`).with({
path: '/vscode-remote-resource',
query: `tkn=${options.remoteConnectionData.connectionToken}&path=${encodeURIComponent(resourceToLoad.path)}`,
});

const response = await requestService.request({ url: uri.toString(true) }, CancellationToken.None);
if (response.res.statusCode === 200) {
return new WebviewResourceResponse.StreamSuccess(response.stream, mime);
}
return WebviewResourceResponse.Failed;
}
}

try {
const contents = await fileService.readFileStream(resourceToLoad);
const mime = getWebviewContentMimeType(requestUri); // Use the original path for the mime
return new WebviewResourceResponse.StreamSuccess(contents.value, mime);
} catch (err) {
console.log(err);
Expand All @@ -90,20 +113,7 @@ function getResourceToLoad(
const normalizedPath = normalizeRequestPath(requestUri);

for (const root of roots) {
if (!containsResource(root, normalizedPath)) {
continue;
}

if (extensionLocation && extensionLocation.scheme === REMOTE_HOST_SCHEME) {
return URI.from({
scheme: REMOTE_HOST_SCHEME,
authority: extensionLocation.authority,
path: '/vscode-resource',
query: JSON.stringify({
requestResourcePath: normalizedPath.path
})
});
} else {
if (containsResource(root, normalizedPath)) {
return normalizedPath;
}
}
Expand All @@ -123,19 +133,6 @@ function normalizeRequestPath(requestUri: URI) {
query: requestUri.query,
fragment: requestUri.fragment
});
} else if (requestUri.scheme === Schemas.oldVscodeWebviewResource) {
// Modern `vscode-resource` uris puts the scheme as the authority
if (requestUri.authority) {
const resourceUri = URI.parse(`${requestUri.authority}:${encodeURIComponent(requestUri.path).replace(/%2F/g, '/')}`);
return resourceUri.with({
query: requestUri.query,
fragment: requestUri.fragment
});
}

// Old style vscode-resource uris lose the scheme of the resource which means they are unable to
// load a mix of local and remote content properly.
return requestUri.with({ scheme: 'file' });
} else {
return requestUri;
}
Expand Down
4 changes: 3 additions & 1 deletion src/vs/platform/webview/common/webviewManagerService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import { UriComponents } from 'vs/base/common/uri';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IRemoteConnectionData } from 'vs/platform/remote/common/remoteAuthorityResolver';

export const IWebviewManagerService = createDecorator<IWebviewManagerService>('webviewManagerService');

Expand All @@ -13,12 +14,13 @@ export interface IWebviewManagerService {

registerWebview(id: string, metadata: RegisterWebviewMetadata): Promise<void>;
unregisterWebview(id: string): Promise<void>;
updateLocalResourceRoots(id: string, roots: UriComponents[]): Promise<void>;
updateWebviewMetadata(id: string, metadataDelta: Partial<RegisterWebviewMetadata>): Promise<void>;

setIgnoreMenuShortcuts(webContentsId: number, enabled: boolean): Promise<void>;
}

export interface RegisterWebviewMetadata {
readonly extensionLocation: UriComponents | undefined;
readonly localResourceRoots: readonly UriComponents[];
readonly remoteConnectionData: IRemoteConnectionData | null;
}
25 changes: 16 additions & 9 deletions src/vs/platform/webview/electron-main/webviewMainService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
*--------------------------------------------------------------------------------------------*/

import { webContents } from 'electron';
import { URI } from 'vs/base/common/uri';
import { IFileService } from 'vs/platform/files/common/files';
import { IRequestService } from 'vs/platform/request/common/request';
import { IWebviewManagerService, RegisterWebviewMetadata } from 'vs/platform/webview/common/webviewManagerService';
import { WebviewProtocolProvider } from 'vs/platform/webview/electron-main/webviewProtocolProvider';
import { IFileService } from 'vs/platform/files/common/files';
import { UriComponents, URI } from 'vs/base/common/uri';

export class WebviewMainService implements IWebviewManagerService {

Expand All @@ -17,23 +18,29 @@ export class WebviewMainService implements IWebviewManagerService {

constructor(
@IFileService fileService: IFileService,
@IRequestService requestService: IRequestService,
) {
this.protocolProvider = new WebviewProtocolProvider(fileService);
this.protocolProvider = new WebviewProtocolProvider(fileService, requestService);
}

public async registerWebview(id: string, metadata: RegisterWebviewMetadata): Promise<void> {
this.protocolProvider.registerWebview(id,
metadata.extensionLocation ? URI.from(metadata.extensionLocation) : undefined,
metadata.localResourceRoots.map((x: UriComponents) => URI.from(x))
);
this.protocolProvider.registerWebview(id, {
...metadata,
extensionLocation: metadata.extensionLocation ? URI.from(metadata.extensionLocation) : undefined,
localResourceRoots: metadata.localResourceRoots.map(x => URI.from(x))
});
}

public async unregisterWebview(id: string): Promise<void> {
this.protocolProvider.unreigsterWebview(id);
}

public async updateLocalResourceRoots(id: string, roots: UriComponents[]): Promise<void> {
this.protocolProvider.updateLocalResourceRoots(id, roots.map((x: UriComponents) => URI.from(x)));
public async updateWebviewMetadata(id: string, metadataDelta: Partial<RegisterWebviewMetadata>): Promise<void> {
this.protocolProvider.updateWebviewMetadata(id, {
...metadataDelta,
localResourceRoots: metadataDelta.localResourceRoots?.map(x => URI.from(x)),
extensionLocation: metadataDelta.extensionLocation ? URI.from(metadataDelta.extensionLocation) : undefined,
});
}

public async setIgnoreMenuShortcuts(webContentsId: number, enabled: boolean): Promise<void> {
Expand Down
30 changes: 20 additions & 10 deletions src/vs/platform/webview/electron-main/webviewProtocolProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,23 @@ import { Schemas } from 'vs/base/common/network';
import { URI } from 'vs/base/common/uri';
import { streamToNodeReadable } from 'vs/base/node/stream';
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 { loadLocalResourceStream, WebviewResourceResponse } from 'vs/platform/webview/common/resourceLoader';

interface WebviewMetadata {
readonly extensionLocation: URI | undefined;
readonly localResourceRoots: readonly URI[];
readonly remoteConnectionData: IRemoteConnectionData | null;
}

export class WebviewProtocolProvider extends Disposable {

private readonly webviewMetadata = new Map<string, {
readonly extensionLocation: URI | undefined;
readonly localResourceRoots: readonly URI[];
}>();
private readonly webviewMetadata = new Map<string, WebviewMetadata>();

constructor(
@IFileService private readonly fileService: IFileService,
@IRequestService private readonly requestService: IRequestService,
) {
super();

Expand All @@ -30,7 +36,11 @@ export class WebviewProtocolProvider extends Disposable {
const id = uri.authority;
const metadata = this.webviewMetadata.get(id);
if (metadata) {
const result = await loadLocalResourceStream(uri, this.fileService, metadata.extensionLocation, metadata.localResourceRoots);
const result = await loadLocalResourceStream(uri, {
extensionLocation: metadata.extensionLocation,
roots: metadata.localResourceRoots,
remoteConnectionData: metadata.remoteConnectionData,
}, this.fileService, this.requestService);
if (result.type === WebviewResourceResponse.Type.Success) {
return callback({
statusCode: 200,
Expand All @@ -57,20 +67,20 @@ export class WebviewProtocolProvider extends Disposable {
this._register(toDisposable(() => protocol.unregisterProtocol(Schemas.vscodeWebviewResource)));
}

public registerWebview(id: string, extensionLocation: URI | undefined, localResourceRoots: readonly URI[]): void {
this.webviewMetadata.set(id, { extensionLocation, localResourceRoots });
public async registerWebview(id: string, metadata: WebviewMetadata): Promise<void> {
this.webviewMetadata.set(id, metadata);
}

public unreigsterWebview(id: string): void {
this.webviewMetadata.delete(id);
}

public updateLocalResourceRoots(id: string, localResourceRoots: readonly URI[]) {
public async updateWebviewMetadata(id: string, metadataDelta: Partial<WebviewMetadata>): Promise<void> {
const entry = this.webviewMetadata.get(id);
if (entry) {
this.webviewMetadata.set(id, {
extensionLocation: entry.extensionLocation,
localResourceRoots: localResourceRoots,
...entry,
...metadataDelta,
});
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
newFrame.contentWindow.addEventListener('mousemove', tryDispatchSyntheticMouseEvent);
},
rewriteCSP: (csp) => {
return csp;
return csp.replace(/vscode-resource:(?=(\s|;|$))/g, 'vscode-webview-resource:');
},
};

Expand Down
Loading

0 comments on commit d2ae606

Please sign in to comment.