提交 5106b556 编写于 作者: M Matt Bierner

Support loading webviews from wildcard endpoints

Fixes #77132

Add support for loading webviews from and endpoint that looks like:

```
https://{{uuid}}.contoso.com/path/to/some/commit/index.html
```

This lets us serve each webview from a seperate origin
上级 8536df96
......@@ -88,7 +88,7 @@ export class MainThreadEditorInsets implements MainThreadEditorInsetsShape {
const disposables = new DisposableStore();
const webview = this._webviewService.createWebview({
const webview = this._webviewService.createWebview('' + handle, {
enableFindWidget: false,
allowSvgs: false,
extension: { id: extensionId, location: URI.revive(extensionLocation) }
......
......@@ -93,7 +93,7 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews
mainThreadShowOptions.group = viewColumnToEditorGroup(this._editorGroupService, showOptions.viewColumn);
}
const webview = this._webviewEditorService.createWebview(this.getInternalWebviewId(viewType), title, mainThreadShowOptions, reviveWebviewOptions(options), {
const webview = this._webviewEditorService.createWebview(handle, this.getInternalWebviewViewType(viewType), title, mainThreadShowOptions, reviveWebviewOptions(options), {
location: URI.revive(extensionLocation),
id: extensionId
}, this.createWebviewEventDelegate(handle)) as WebviewEditorInput<MainThreadWebviewState>;
......@@ -212,7 +212,7 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews
this._revivers.delete(viewType);
}
private getInternalWebviewId(viewType: string): string {
private getInternalWebviewViewType(viewType: string): string {
return `mainThreadWebview-${viewType}`;
}
......
......@@ -40,7 +40,7 @@ export class ExtHostWebview implements vscode.Webview {
}
public get cspSource(): string {
return this._initData.webviewCspSource;
return this._initData.webviewCspSource.replace('{{uuid}}', this._handle);
}
public get html(): string {
......
......@@ -26,6 +26,7 @@ import { KeybindingParser } from 'vs/base/common/keybindingParser';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { generateUuid } from 'vs/base/common/uuid';
function renderBody(
body: string,
......@@ -91,6 +92,7 @@ export class ReleaseNotesManager {
this._webviewEditorService.revealWebview(this._currentReleaseNotes, activeControl ? activeControl.group : this._editorGroupService.activeGroup, false);
} else {
this._currentReleaseNotes = this._webviewEditorService.createWebview(
generateUuid(),
'releaseNotes',
title,
{ group: ACTIVE_GROUP, preserveFocus: false },
......
......@@ -4,7 +4,7 @@
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy"
content="default-src 'none'; script-src 'self'; child-src https://localhost:* http://localhost:*; style-src 'unsafe-inline';" />
content="default-src 'none'; script-src 'self'; frame-src 'self'; style-src 'unsafe-inline'; worker-src 'self';" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
......
......@@ -367,7 +367,7 @@
// seeing the service worker applying properly.
// Fake load an empty on the correct origin and then write real html
// into it to get around this.
newFrame.src = `/fake.html?id=${ID}`;
newFrame.src = `./fake.html?id=${ID}`;
}
newFrame.style.cssText = 'display: block; margin: 0; overflow: hidden; position: absolute; width: 100%; height: 100%; visibility: hidden';
document.body.appendChild(newFrame);
......
......@@ -4,10 +4,12 @@
*--------------------------------------------------------------------------------------------*/
const VERSION = 1;
const rootPath = self.location.pathname.replace(/\/service-worker.js$/, '');
/**
* Root path for resources
*/
const resourceRoot = '/vscode-resource';
const resourceRoot = rootPath + '/vscode-resource';
const resolveTimeout = 30000;
......@@ -179,7 +181,7 @@ async function processResourceRequest(event, requestUrl) {
}
const webviewId = getWebviewIdForClient(client);
const resourcePath = requestUrl.pathname.replace(resourceRoot, '');
const resourcePath = requestUrl.pathname.startsWith(resourceRoot + '/') ? requestUrl.pathname.slice(resourceRoot.length) : requestUrl.pathname;
function resolveResourceEntry(entry) {
if (!entry) {
......@@ -269,6 +271,6 @@ async function getOuterIframeClient(webviewId) {
const allClients = await self.clients.matchAll({ includeUncontrolled: true });
return allClients.find(client => {
const clientUrl = new URL(client.url);
return clientUrl.pathname === '/' && clientUrl.search.match(new RegExp('\\bid=' + webviewId));
return (clientUrl.pathname === `${rootPath}/` || clientUrl.pathname === `${rootPath}/index.html`) && clientUrl.search.match(new RegExp('\\bid=' + webviewId));
});
}
\ No newline at end of file
......@@ -243,7 +243,7 @@ export class WebviewEditor extends BaseEditor {
this.findWidgetVisible = KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE.bindTo(this._contextKeyService);
}
this._webview = this._webviewService.createWebview(
this._webview = this._webviewService.createWebview(input.id,
{
allowSvgs: true,
extension: input.extension,
......
......@@ -14,14 +14,13 @@ import { WebviewEvents, WebviewInputOptions } from './webviewEditorService';
import { Webview, WebviewOptions } from 'vs/workbench/contrib/webview/common/webview';
export class WebviewEditorInput<State = any> extends EditorInput {
private static handlePool = 0;
private static _styleElement?: HTMLStyleElement;
private static _icons = new Map<number, { light: URI, dark: URI }>();
private static _icons = new Map<string, { light: URI, dark: URI }>();
private static updateStyleElement(
id: number,
id: string,
iconPath: { light: URI, dark: URI } | undefined
) {
if (!this._styleElement) {
......@@ -68,9 +67,9 @@ export class WebviewEditorInput<State = any> extends EditorInput {
readonly location: URI;
readonly id: ExtensionIdentifier;
};
private readonly _id: number;
constructor(
public readonly id: string,
public readonly viewType: string,
name: string,
options: WebviewInputOptions,
......@@ -84,8 +83,6 @@ export class WebviewEditorInput<State = any> extends EditorInput {
) {
super();
this._id = WebviewEditorInput.handlePool++;
this._name = name;
this._options = options;
this._events = events;
......@@ -120,7 +117,7 @@ export class WebviewEditorInput<State = any> extends EditorInput {
public getResource(): URI {
return URI.from({
scheme: 'webview-panel',
path: `webview-panel/webview-${this._id}`
path: `webview-panel/webview-${this.id}`
});
}
......@@ -147,7 +144,7 @@ export class WebviewEditorInput<State = any> extends EditorInput {
public set iconPath(value: { light: URI, dark: URI } | undefined) {
this._iconPath = value;
WebviewEditorInput.updateStyleElement(this._id, value);
WebviewEditorInput.updateStyleElement(this.id, value);
}
public matches(other: IEditorInput): boolean {
......@@ -213,7 +210,7 @@ export class WebviewEditorInput<State = any> extends EditorInput {
public get container(): HTMLElement {
if (!this._container) {
this._container = document.createElement('div');
this._container.id = `webview-${this._id}`;
this._container.id = `webview-${this.id}`;
const part = this._layoutService.getContainer(Parts.EDITOR_PART);
part.appendChild(this._container);
}
......@@ -301,6 +298,7 @@ export class RevivedWebviewEditorInput extends WebviewEditorInput {
private _revived: boolean = false;
constructor(
id: string,
viewType: string,
name: string,
options: WebviewInputOptions,
......@@ -313,7 +311,7 @@ export class RevivedWebviewEditorInput extends WebviewEditorInput {
private readonly reviver: (input: WebviewEditorInput) => Promise<void>,
@IWorkbenchLayoutService partService: IWorkbenchLayoutService,
) {
super(viewType, name, options, state, events, extension, partService);
super(id, viewType, name, options, state, events, extension, partService);
}
public async resolve(): Promise<IEditorModel> {
......
......@@ -9,6 +9,7 @@ import { WebviewEditorInput } from './webviewEditorInput';
import { IWebviewEditorService, WebviewInputOptions } from './webviewEditorService';
import { URI, UriComponents } from 'vs/base/common/uri';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { generateUuid } from 'vs/base/common/uuid';
interface SerializedIconPath {
light: string | UriComponents;
......@@ -67,7 +68,7 @@ export class WebviewEditorInputFactory implements IEditorInputFactory {
const extensionLocation = reviveUri(data.extensionLocation);
const extensionId = data.extensionId ? new ExtensionIdentifier(data.extensionId) : undefined;
const iconPath = reviveIconPath(data.iconPath);
return this._webviewService.reviveWebview(data.viewType, data.title, iconPath, data.state, data.options, extensionLocation ? {
return this._webviewService.reviveWebview(generateUuid(), data.viewType, data.title, iconPath, data.state, data.options, extensionLocation ? {
location: extensionLocation,
id: extensionId
} : undefined, data.group);
......
......@@ -26,6 +26,7 @@ export interface IWebviewEditorService {
_serviceBrand: any;
createWebview(
id: string,
viewType: string,
title: string,
showOptions: ICreateWebViewShowOptions,
......@@ -38,6 +39,7 @@ export interface IWebviewEditorService {
): WebviewEditorInput;
reviveWebview(
id: string,
viewType: string,
title: string,
iconPath: { light: URI, dark: URI } | undefined,
......@@ -133,6 +135,7 @@ export class WebviewEditorService implements IWebviewEditorService {
) { }
public createWebview(
id: string,
viewType: string,
title: string,
showOptions: ICreateWebViewShowOptions,
......@@ -143,7 +146,7 @@ export class WebviewEditorService implements IWebviewEditorService {
},
events: WebviewEvents
): WebviewEditorInput {
const webviewInput = this._instantiationService.createInstance(WebviewEditorInput, viewType, title, options, {}, events, extension);
const webviewInput = this._instantiationService.createInstance(WebviewEditorInput, id, viewType, title, options, {}, events, extension);
this._editorService.openEditor(webviewInput, { pinned: true, preserveFocus: showOptions.preserveFocus }, showOptions.group);
return webviewInput;
}
......@@ -164,6 +167,7 @@ export class WebviewEditorService implements IWebviewEditorService {
}
public reviveWebview(
id: string,
viewType: string,
title: string,
iconPath: { light: URI, dark: URI } | undefined,
......@@ -175,7 +179,7 @@ export class WebviewEditorService implements IWebviewEditorService {
},
group: number | undefined,
): WebviewEditorInput {
const webviewInput = this._instantiationService.createInstance(RevivedWebviewEditorInput, viewType, title, options, state, {}, extension, async (webview: WebviewEditorInput): Promise<void> => {
const webviewInput = this._instantiationService.createInstance(RevivedWebviewEditorInput, id, viewType, title, options, state, {}, extension, async (webview: WebviewEditorInput): Promise<void> => {
const didRevive = await this.tryRevive(webview);
if (didRevive) {
return Promise.resolve(undefined);
......
......@@ -32,11 +32,10 @@ export class IFrameWebview extends Disposable implements Webview {
private content: WebviewContent;
private _focused = false;
private readonly id: string;
private readonly _portMappingManager: WebviewPortMappingManager;
constructor(
private readonly id: string,
private _options: WebviewOptions,
contentOptions: WebviewContentOptions,
@IThemeService themeService: IThemeService,
......@@ -62,11 +61,9 @@ export class IFrameWebview extends Disposable implements Webview {
state: undefined
};
this.id = `webview-${Date.now()}`;
this.element = document.createElement('iframe');
this.element.sandbox.add('allow-scripts', 'allow-same-origin');
this.element.setAttribute('src', `${environmentService.webviewEndpoint}?id=${this.id}`);
this.element.setAttribute('src', `${this.endpoint}/index.html?id=${this.id}`);
this.element.style.border = 'none';
this.element.style.width = '100%';
this.element.style.height = '100%';
......@@ -144,6 +141,14 @@ export class IFrameWebview extends Disposable implements Webview {
this._register(themeService.onThemeChange(this.style, this));
}
private get endpoint(): string {
const endpoint = this.environmentService.webviewEndpoint!.replace('{{uuid}}', this.id);
if (endpoint[endpoint.length - 1] === '/') {
return endpoint.slice(0, endpoint.length - 1);
}
return endpoint;
}
public mountTo(parent: HTMLElement) {
if (this.element) {
parent.appendChild(this.element);
......@@ -174,7 +179,7 @@ export class IFrameWebview extends Disposable implements Webview {
private preprocessHtml(value: string): string {
return value.replace(/(["'])vscode-resource:([^\s'"]+?)(["'])/gi, (_, startQuote, path, endQuote) =>
`${startQuote}${this.environmentService.webviewEndpoint}/vscode-resource${path}${endQuote}`);
`${startQuote}${this.endpoint}/vscode-resource${path}${endQuote}`);
}
public update(html: string, options: WebviewContentOptions, retainContextWhenHidden: boolean) {
......
......@@ -15,10 +15,12 @@ export class WebviewService implements IWebviewService {
) { }
createWebview(
id: string,
options: WebviewOptions,
contentOptions: WebviewContentOptions
): Webview {
return this._instantiationService.createInstance(WebviewElement,
id,
options,
contentOptions);
}
......
......@@ -26,6 +26,7 @@ export interface IWebviewService {
_serviceBrand: any;
createWebview(
id: string,
options: WebviewOptions,
contentOptions: WebviewContentOptions,
): Webview;
......
......@@ -15,6 +15,7 @@ export class WebviewService implements IWebviewService {
) { }
createWebview(
_id: string,
options: WebviewOptions,
contentOptions: WebviewContentOptions
): Webview {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册