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:
parent
8ebaa0c0f1
commit
af5002501a
5 changed files with 73 additions and 41 deletions
|
@ -60,6 +60,8 @@ export namespace Schemas {
|
|||
|
||||
export const webviewPanel = 'webview-panel';
|
||||
|
||||
export const oldVscodeWebviewResource = 'vscode-resource';
|
||||
|
||||
export const vscodeWebviewResource = 'vscode-webview-resource';
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -64,7 +64,7 @@
|
|||
newFrame.contentWindow.addEventListener('mousemove', tryDispatchSyntheticMouseEvent);
|
||||
},
|
||||
rewriteCSP: (csp) => {
|
||||
return csp.replace(/vscode-resource:(?=(\s|;|$))/g, 'vscode-webview-resource:');
|
||||
return csp;
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 }); }
|
||||
|
|
Loading…
Reference in a new issue