Move back to use renderer-side protocol for loading resources in webviews

Fixes #98768

The protocol in the main process cannot correctly load remote resources from the workspace at the moment. Reverting to use the renderer side protocol which can handle this correctly
This commit is contained in:
Matt Bierner 2020-06-01 15:24:41 -07:00
parent 8ebaa0c0f1
commit af5002501a
5 changed files with 73 additions and 41 deletions

View file

@ -60,6 +60,8 @@ export namespace Schemas {
export const webviewPanel = 'webview-panel';
export const oldVscodeWebviewResource = 'vscode-resource';
export const vscodeWebviewResource = 'vscode-webview-resource';
}

View file

@ -112,20 +112,27 @@ function getResourceToLoad(
}
function normalizeRequestPath(requestUri: URI) {
if (requestUri.scheme !== Schemas.vscodeWebviewResource) {
if (requestUri.scheme === Schemas.vscodeWebviewResource) {
// The `vscode-webview-resource` scheme has the following format:
//
// vscode-webview-resource://id/scheme//authority?/path
//
const resourceUri = URI.parse(requestUri.path.replace(/^\/([a-z0-9\-]+)\/{1,2}/i, '$1://'));
return resourceUri.with({
query: requestUri.query,
fragment: requestUri.fragment
});
} else if (requestUri.scheme === Schemas.oldVscodeWebviewResource) {
// The `vscode-resource` puts the scheme as the authority
const resourceUri = URI.parse(`${requestUri.authority}:${encodeURIComponent(requestUri.path).replace(/%2F/g, '/')}`);
return resourceUri.with({
query: requestUri.query,
fragment: requestUri.fragment
});
} else {
return requestUri;
}
// The `vscode-webview-resource` scheme has the following format:
//
// vscode-webview-resource://id/scheme//authority?/path
//
const resourceUri = URI.parse(requestUri.path.replace(/^\/([a-z0-9\-]+)\/{1,2}/i, '$1://'));
return resourceUri.with({
query: requestUri.query,
fragment: requestUri.fragment
});
}
function containsResource(root: URI, resource: URI): boolean {

View file

@ -64,7 +64,7 @@
newFrame.contentWindow.addEventListener('mousemove', tryDispatchSyntheticMouseEvent);
},
rewriteCSP: (csp) => {
return csp.replace(/vscode-resource:(?=(\s|;|$))/g, 'vscode-webview-resource:');
return csp;
},
};

View file

@ -22,6 +22,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
import { ITunnelService } from 'vs/platform/remote/common/tunnel';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { loadLocalResource, WebviewResourceResponse } from 'vs/platform/webview/common/resourceLoader';
import { IWebviewManagerService } from 'vs/platform/webview/common/webviewManagerService';
import { BaseWebview, WebviewMessageChannels } from 'vs/workbench/contrib/webview/browser/baseWebviewElement';
import { Webview, WebviewContentOptions, WebviewExtensionDescription, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview';
@ -30,6 +31,34 @@ import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/common/th
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { WebviewFindDelegate, WebviewFindWidget } from '../browser/webviewFindWidget';
export function registerFileProtocol(
contents: WebContents,
protocol: string,
fileService: IFileService,
extensionLocation: URI | undefined,
getRoots: () => ReadonlyArray<URI>
) {
contents.session.protocol.registerBufferProtocol(protocol, async (request, callback: any) => {
try {
const result = await loadLocalResource(URI.parse(request.url), fileService, extensionLocation, getRoots());
if (result.type === WebviewResourceResponse.Type.Success) {
return callback({
data: Buffer.from(result.buffer.buffer),
mimeType: result.mimeType
});
}
if (result.type === WebviewResourceResponse.Type.AccessDenied) {
console.error('Webview: Cannot load resource outside of protocol root');
return callback({ error: -10 /* ACCESS_DENIED: https://cs.chromium.org/chromium/src/net/base/net_error_list.h */ });
}
} catch {
// noop
}
return callback({ error: -2 /* FAILED: https://cs.chromium.org/chromium/src/net/base/net_error_list.h */ });
});
}
class WebviewTagHandle extends Disposable {
private _webContents: undefined | WebContents | 'destroyed';
@ -123,21 +152,25 @@ class WebviewProtocolProvider extends Disposable {
private _localResourceRoots: ReadonlyArray<URI>;
constructor(
private readonly id: string,
private readonly extension: WebviewExtensionDescription | undefined,
handle: WebviewTagHandle,
extension: WebviewExtensionDescription | undefined,
initialLocalResourceRoots: ReadonlyArray<URI>,
private readonly _webviewManagerService: IWebviewManagerService,
fileService: IFileService,
) {
super();
this._localResourceRoots = initialLocalResourceRoots;
this._ready = _webviewManagerService.registerWebview(this.id, {
extensionLocation: this.extension?.location.toJSON(),
localResourceRoots: initialLocalResourceRoots.map(x => x.toJSON()),
this._ready = new Promise((resolve, reject) => {
this._register(handle.onFirstLoad(contents => {
try {
registerFileProtocol(contents, Schemas.oldVscodeWebviewResource, fileService, extension?.location, () => this._localResourceRoots);
resolve();
} catch {
reject();
}
}));
});
this._register(toDisposable(() => this._webviewManagerService.unregisterWebview(this.id)));
}
public update(localResourceRoots: ReadonlyArray<URI>) {
@ -146,8 +179,6 @@ class WebviewProtocolProvider extends Disposable {
}
this._localResourceRoots = localResourceRoots;
this._ready = this._webviewManagerService.updateLocalResourceRoots(this.id, localResourceRoots.map(x => x.toJSON()));
}
async synchronize(): Promise<void> {
@ -274,12 +305,10 @@ export class ElectronWebviewBasedWebview extends BaseWebview<WebviewTag> impleme
) {
super(id, options, contentOptions, extension, _webviewThemeDataProvider, telemetryService, environementService, workbenchEnvironmentService);
const webviewManagerService = createChannelSender<IWebviewManagerService>(mainProcessService.getChannel('webview'));
const webviewAndContents = this._register(new WebviewTagHandle(this.element!));
const session = this._register(new WebviewSession(webviewAndContents));
this._protocolProvider = this._register(new WebviewProtocolProvider(id, extension, this.content.options.localResourceRoots || [], webviewManagerService));
this._protocolProvider = this._register(new WebviewProtocolProvider(webviewAndContents, extension, this.content.options.localResourceRoots || [], fileService));
this._register(new WebviewPortMappingProvider(
session,
@ -361,6 +390,7 @@ export class ElectronWebviewBasedWebview extends BaseWebview<WebviewTag> impleme
element.focus = () => {
this.doFocus();
};
element.setAttribute('partition', `webview${Date.now()}`);
element.setAttribute('webpreferences', 'contextIsolation=yes');
element.className = `webview ${options.customClasses || ''}`;
@ -371,7 +401,6 @@ export class ElectronWebviewBasedWebview extends BaseWebview<WebviewTag> impleme
element.preload = require.toUrl('./pre/electron-index.js');
element.src = 'data:text/html;charset=utf-8,%3C%21DOCTYPE%20html%3E%0D%0A%3Chtml%20lang%3D%22en%22%20style%3D%22width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3Chead%3E%0D%0A%3Ctitle%3EVirtual%20Document%3C%2Ftitle%3E%0D%0A%3C%2Fhead%3E%0D%0A%3Cbody%20style%3D%22margin%3A%200%3B%20overflow%3A%20hidden%3B%20width%3A%20100%25%3B%20height%3A%20100%25%22%20role%3D%22document%22%3E%0D%0A%3C%2Fbody%3E%0D%0A%3C%2Fhtml%3E';
return element;
}
@ -392,20 +421,9 @@ export class ElectronWebviewBasedWebview extends BaseWebview<WebviewTag> impleme
}
private preprocessHtml(value: string): string {
return value
.replace(/(["'])vscode-resource:(\/\/([^\s\/'"]+?)(?=\/))?([^\s'"]+?)(["'])/gi, (match, startQuote, _1, scheme, path, endQuote) => {
if (scheme) {
return `${startQuote}${Schemas.vscodeWebviewResource}://${this.id}/${scheme}${path}${endQuote}`;
}
if (!path.startsWith('//')) {
// Add an empty authority if we don't already have one
path = '//' + path;
}
return `${startQuote}${Schemas.vscodeWebviewResource}://${this.id}/file${path}${endQuote}`;
});
return value;
}
public mountTo(parent: HTMLElement) {
if (!this.element) {
return;

View file

@ -42,10 +42,15 @@ export class NativeWorkbenchEnvironmentService extends EnvironmentService implem
}
@memoize
get webviewResourceRoot(): string { return `${Schemas.vscodeWebviewResource}://{{uuid}}/{{resource}}`; }
get webviewResourceRoot(): string {
return `${Schemas.oldVscodeWebviewResource}://{{resource}}`;
// TODO mjbvz: restore when switching to new protcol
// return `${Schemas.vscodeWebviewResource}://{{uuid}}/{{resource}}`;
}
@memoize
get webviewCspSource(): string { return `${Schemas.vscodeWebviewResource}:`; }
get webviewCspSource(): string { return `${Schemas.oldVscodeWebviewResource}:`; }
@memoize
get userRoamingDataHome(): URI { return this.appSettingsHome.with({ scheme: Schemas.userData }); }