Skip to content

Commit af50025

Browse files
committed
Move back to use renderer-side protocol for loading resources in webviews
Fixes microsoft#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
1 parent 8ebaa0c commit af50025

5 files changed

Lines changed: 73 additions & 41 deletions

File tree

src/vs/base/common/network.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ export namespace Schemas {
6060

6161
export const webviewPanel = 'webview-panel';
6262

63+
export const oldVscodeWebviewResource = 'vscode-resource';
64+
6365
export const vscodeWebviewResource = 'vscode-webview-resource';
6466
}
6567

src/vs/platform/webview/common/resourceLoader.ts

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -112,20 +112,27 @@ function getResourceToLoad(
112112
}
113113

114114
function normalizeRequestPath(requestUri: URI) {
115-
if (requestUri.scheme !== Schemas.vscodeWebviewResource) {
115+
if (requestUri.scheme === Schemas.vscodeWebviewResource) {
116+
// The `vscode-webview-resource` scheme has the following format:
117+
//
118+
// vscode-webview-resource://id/scheme//authority?/path
119+
//
120+
const resourceUri = URI.parse(requestUri.path.replace(/^\/([a-z0-9\-]+)\/{1,2}/i, '$1://'));
121+
122+
return resourceUri.with({
123+
query: requestUri.query,
124+
fragment: requestUri.fragment
125+
});
126+
} else if (requestUri.scheme === Schemas.oldVscodeWebviewResource) {
127+
// The `vscode-resource` puts the scheme as the authority
128+
const resourceUri = URI.parse(`${requestUri.authority}:${encodeURIComponent(requestUri.path).replace(/%2F/g, '/')}`);
129+
return resourceUri.with({
130+
query: requestUri.query,
131+
fragment: requestUri.fragment
132+
});
133+
} else {
116134
return requestUri;
117135
}
118-
119-
// The `vscode-webview-resource` scheme has the following format:
120-
//
121-
// vscode-webview-resource://id/scheme//authority?/path
122-
//
123-
const resourceUri = URI.parse(requestUri.path.replace(/^\/([a-z0-9\-]+)\/{1,2}/i, '$1://'));
124-
125-
return resourceUri.with({
126-
query: requestUri.query,
127-
fragment: requestUri.fragment
128-
});
129136
}
130137

131138
function containsResource(root: URI, resource: URI): boolean {

src/vs/workbench/contrib/webview/electron-browser/pre/electron-index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
newFrame.contentWindow.addEventListener('mousemove', tryDispatchSyntheticMouseEvent);
6565
},
6666
rewriteCSP: (csp) => {
67-
return csp.replace(/vscode-resource:(?=(\s|;|$))/g, 'vscode-webview-resource:');
67+
return csp;
6868
},
6969
};
7070

src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
2222
import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
2323
import { ITunnelService } from 'vs/platform/remote/common/tunnel';
2424
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
25+
import { loadLocalResource, WebviewResourceResponse } from 'vs/platform/webview/common/resourceLoader';
2526
import { IWebviewManagerService } from 'vs/platform/webview/common/webviewManagerService';
2627
import { BaseWebview, WebviewMessageChannels } from 'vs/workbench/contrib/webview/browser/baseWebviewElement';
2728
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
3031
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
3132
import { WebviewFindDelegate, WebviewFindWidget } from '../browser/webviewFindWidget';
3233

34+
export function registerFileProtocol(
35+
contents: WebContents,
36+
protocol: string,
37+
fileService: IFileService,
38+
extensionLocation: URI | undefined,
39+
getRoots: () => ReadonlyArray<URI>
40+
) {
41+
contents.session.protocol.registerBufferProtocol(protocol, async (request, callback: any) => {
42+
try {
43+
const result = await loadLocalResource(URI.parse(request.url), fileService, extensionLocation, getRoots());
44+
if (result.type === WebviewResourceResponse.Type.Success) {
45+
return callback({
46+
data: Buffer.from(result.buffer.buffer),
47+
mimeType: result.mimeType
48+
});
49+
}
50+
if (result.type === WebviewResourceResponse.Type.AccessDenied) {
51+
console.error('Webview: Cannot load resource outside of protocol root');
52+
return callback({ error: -10 /* ACCESS_DENIED: https://cs.chromium.org/chromium/src/net/base/net_error_list.h */ });
53+
}
54+
} catch {
55+
// noop
56+
}
57+
58+
return callback({ error: -2 /* FAILED: https://cs.chromium.org/chromium/src/net/base/net_error_list.h */ });
59+
});
60+
}
61+
3362
class WebviewTagHandle extends Disposable {
3463

3564
private _webContents: undefined | WebContents | 'destroyed';
@@ -123,21 +152,25 @@ class WebviewProtocolProvider extends Disposable {
123152
private _localResourceRoots: ReadonlyArray<URI>;
124153

125154
constructor(
126-
private readonly id: string,
127-
private readonly extension: WebviewExtensionDescription | undefined,
155+
handle: WebviewTagHandle,
156+
extension: WebviewExtensionDescription | undefined,
128157
initialLocalResourceRoots: ReadonlyArray<URI>,
129-
private readonly _webviewManagerService: IWebviewManagerService,
158+
fileService: IFileService,
130159
) {
131160
super();
132161

133162
this._localResourceRoots = initialLocalResourceRoots;
134163

135-
this._ready = _webviewManagerService.registerWebview(this.id, {
136-
extensionLocation: this.extension?.location.toJSON(),
137-
localResourceRoots: initialLocalResourceRoots.map(x => x.toJSON()),
164+
this._ready = new Promise((resolve, reject) => {
165+
this._register(handle.onFirstLoad(contents => {
166+
try {
167+
registerFileProtocol(contents, Schemas.oldVscodeWebviewResource, fileService, extension?.location, () => this._localResourceRoots);
168+
resolve();
169+
} catch {
170+
reject();
171+
}
172+
}));
138173
});
139-
140-
this._register(toDisposable(() => this._webviewManagerService.unregisterWebview(this.id)));
141174
}
142175

143176
public update(localResourceRoots: ReadonlyArray<URI>) {
@@ -146,8 +179,6 @@ class WebviewProtocolProvider extends Disposable {
146179
}
147180

148181
this._localResourceRoots = localResourceRoots;
149-
150-
this._ready = this._webviewManagerService.updateLocalResourceRoots(this.id, localResourceRoots.map(x => x.toJSON()));
151182
}
152183

153184
async synchronize(): Promise<void> {
@@ -274,12 +305,10 @@ export class ElectronWebviewBasedWebview extends BaseWebview<WebviewTag> impleme
274305
) {
275306
super(id, options, contentOptions, extension, _webviewThemeDataProvider, telemetryService, environementService, workbenchEnvironmentService);
276307

277-
const webviewManagerService = createChannelSender<IWebviewManagerService>(mainProcessService.getChannel('webview'));
278-
279308
const webviewAndContents = this._register(new WebviewTagHandle(this.element!));
280309
const session = this._register(new WebviewSession(webviewAndContents));
281310

282-
this._protocolProvider = this._register(new WebviewProtocolProvider(id, extension, this.content.options.localResourceRoots || [], webviewManagerService));
311+
this._protocolProvider = this._register(new WebviewProtocolProvider(webviewAndContents, extension, this.content.options.localResourceRoots || [], fileService));
283312

284313
this._register(new WebviewPortMappingProvider(
285314
session,
@@ -361,6 +390,7 @@ export class ElectronWebviewBasedWebview extends BaseWebview<WebviewTag> impleme
361390
element.focus = () => {
362391
this.doFocus();
363392
};
393+
element.setAttribute('partition', `webview${Date.now()}`);
364394
element.setAttribute('webpreferences', 'contextIsolation=yes');
365395
element.className = `webview ${options.customClasses || ''}`;
366396

@@ -371,7 +401,6 @@ export class ElectronWebviewBasedWebview extends BaseWebview<WebviewTag> impleme
371401

372402
element.preload = require.toUrl('./pre/electron-index.js');
373403
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';
374-
375404
return element;
376405
}
377406

@@ -392,20 +421,9 @@ export class ElectronWebviewBasedWebview extends BaseWebview<WebviewTag> impleme
392421
}
393422

394423
private preprocessHtml(value: string): string {
395-
return value
396-
.replace(/(["'])vscode-resource:(\/\/([^\s\/'"]+?)(?=\/))?([^\s'"]+?)(["'])/gi, (match, startQuote, _1, scheme, path, endQuote) => {
397-
if (scheme) {
398-
return `${startQuote}${Schemas.vscodeWebviewResource}://${this.id}/${scheme}${path}${endQuote}`;
399-
}
400-
if (!path.startsWith('//')) {
401-
// Add an empty authority if we don't already have one
402-
path = '//' + path;
403-
}
404-
return `${startQuote}${Schemas.vscodeWebviewResource}://${this.id}/file${path}${endQuote}`;
405-
});
424+
return value;
406425
}
407426

408-
409427
public mountTo(parent: HTMLElement) {
410428
if (!this.element) {
411429
return;

src/vs/workbench/services/environment/electron-browser/environmentService.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,15 @@ export class NativeWorkbenchEnvironmentService extends EnvironmentService implem
4242
}
4343

4444
@memoize
45-
get webviewResourceRoot(): string { return `${Schemas.vscodeWebviewResource}://{{uuid}}/{{resource}}`; }
45+
get webviewResourceRoot(): string {
46+
return `${Schemas.oldVscodeWebviewResource}://{{resource}}`;
47+
48+
// TODO mjbvz: restore when switching to new protcol
49+
// return `${Schemas.vscodeWebviewResource}://{{uuid}}/{{resource}}`;
50+
}
4651

4752
@memoize
48-
get webviewCspSource(): string { return `${Schemas.vscodeWebviewResource}:`; }
53+
get webviewCspSource(): string { return `${Schemas.oldVscodeWebviewResource}:`; }
4954

5055
@memoize
5156
get userRoamingDataHome(): URI { return this.appSettingsHome.with({ scheme: Schemas.userData }); }

0 commit comments

Comments
 (0)