未验证 提交 ce6a5e3c 编写于 作者: M Matt Bierner 提交者: GitHub

Merge pull request #75546 from mjbvz/iframe-webview

Iframe based webview prototype
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy"
content="default-src *; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Virtual Document</title>
</head>
<body>
<script src="main.js"></script>
<script>
(function () {
const id = document.location.search.match(/\bid=([\w\-]+)/)[1];
const handlers = {};
const postMessageToVsCode = (channel, data) => {
window.parent.postMessage({ target: id, channel, data }, '*');
};
window.addEventListener('message', (e) => {
if (e.origin === 'TODO - Does this come from the inner iframe we created?') {
postMessageToVsCode(e.data.command, e.data.data);
return;
}
const channel = e.data.channel;
const handler = handlers[channel];
if (handler) {
handler(e, e.data.args);
} else {
console.log('no handler for ', e);
}
});
createWebviewManager({
origin: document.location.href,
postMessage: (channel, data) => {
postMessageToVsCode(channel, data);
},
onMessage: (channel, handler) => {
handlers[channel] = handler;
},
preProcessHtml: (text) => {
return text.replace(/(?:["'])vscode-resource:([^\s'"]+)(?:["'])/gi, '/resource?path=$1');
}
});
}());
</script>
</body>
</html>
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Emitter } from 'vs/base/common/event';
import { URI } from 'vs/base/common/uri';
import { Webview, WebviewContentOptions, WebviewOptions } from 'vs/workbench/contrib/webview/common/webview';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IFileService } from 'vs/platform/files/common/files';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { Disposable } from 'vs/base/common/lifecycle';
import { areWebviewInputOptionsEqual } from 'vs/workbench/contrib/webview/browser/webviewEditorService';
import { addDisposableListener, addClass } from 'vs/base/browser/dom';
import { getWebviewThemeData } from 'vs/workbench/contrib/webview/common/themeing';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
interface WebviewContent {
readonly html: string;
readonly options: WebviewContentOptions;
readonly state: string | undefined;
}
export class IFrameWebview extends Disposable implements Webview {
private element: HTMLIFrameElement;
private _ready: Promise<void>;
private content: WebviewContent;
private _focused = false;
private readonly id: string;
constructor(
_options: WebviewOptions,
contentOptions: WebviewContentOptions,
@IInstantiationService instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService,
@IEnvironmentService environmentService: IEnvironmentService,
@IFileService fileService: IFileService,
@ITelemetryService telemetryService: ITelemetryService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
) {
super();
this.content = {
html: '',
options: contentOptions,
state: undefined
};
this.id = `webview-${Date.now()}`;
this.element = document.createElement('iframe');
this.element.sandbox.add('allow-scripts');
this.element.sandbox.add('allow-same-origin');
this.element.setAttribute('src', `/src/vs/workbench/contrib/webview/browser/pre/index.html?id=${this.id}`);
this.element.style.border = 'none';
this.element.style.width = '100%';
this.element.style.height = '100%';
this._register(addDisposableListener(window, 'message', e => {
if (!e || !e.data || e.data.target !== this.id) {
return;
}
switch (e.data.channel) {
case 'onmessage':
if (e.data.data) {
this._onMessage.fire(e.data.data);
}
return;
case 'did-click-link':
let [uri] = e.data.data;
this._onDidClickLink.fire(URI.parse(uri));
return;
case 'did-set-content':
// this._webview.style.flex = '';
// this._webview.style.width = '100%';
// this._webview.style.height = '100%';
// this.layout();
return;
case 'did-scroll':
// if (event.args && typeof event.args[0] === 'number') {
// this._onDidScroll.fire({ scrollYPercentage: event.args[0] });
// }
return;
case 'do-reload':
this.reload();
return;
case 'do-update-state':
const state = e.data.data;
this.state = state;
this._onDidUpdateState.fire(state);
return;
case 'did-focus':
this.handleFocusChange(true);
return;
case 'did-blur':
this.handleFocusChange(false);
return;
}
}));
this._ready = new Promise(resolve => {
const subscription = this._register(addDisposableListener(window, 'message', (e) => {
if (e.data && e.data.target === this.id && e.data.channel === 'webview-ready') {
addClass(this.element, 'ready');
subscription.dispose();
resolve();
}
}));
});
this.style(themeService.getTheme());
this._register(themeService.onThemeChange(this.style, this));
}
public mountTo(parent: HTMLElement) {
parent.appendChild(this.element);
}
public set options(options: WebviewContentOptions) {
if (areWebviewInputOptionsEqual(options, this.content.options)) {
return;
}
this.content = {
html: this.content.html,
options: options,
state: this.content.state,
};
this.doUpdateContent();
}
public set html(value: string) {
this.content = {
html: value,
options: this.content.options,
state: this.content.state,
};
this.doUpdateContent();
}
public update(html: string, options: WebviewContentOptions, retainContextWhenHidden: boolean) {
if (retainContextWhenHidden && html === this.content.html && areWebviewInputOptionsEqual(options, this.content.options)) {
return;
}
this.content = {
html: html,
options: options,
state: this.content.state,
};
this.doUpdateContent();
}
private doUpdateContent() {
this._send('content', {
contents: this.content.html,
options: this.content.options,
state: this.content.state
});
}
private handleFocusChange(isFocused: boolean): void {
this._focused = isFocused;
if (this._focused) {
this._onDidFocus.fire();
}
}
initialScrollProgress: number;
state: string | undefined;
private readonly _onDidFocus = this._register(new Emitter<void>());
public readonly onDidFocus = this._onDidFocus.event;
private readonly _onDidClickLink = this._register(new Emitter<URI>());
public readonly onDidClickLink = this._onDidClickLink.event;
private readonly _onDidScroll = this._register(new Emitter<{ scrollYPercentage: number }>());
public readonly onDidScroll = this._onDidScroll.event;
private readonly _onDidUpdateState = this._register(new Emitter<string | undefined>());
public readonly onDidUpdateState = this._onDidUpdateState.event;
private readonly _onMessage = this._register(new Emitter<any>());
public readonly onMessage = this._onMessage.event;
sendMessage(data: any): void {
this._send('message', data);
}
layout(): void {
// noop
}
focus(): void {
this.element.focus();
}
dispose(): void {
if (this.element) {
if (this.element.parentElement) {
this.element.parentElement.removeChild(this.element);
}
}
this.element = undefined!;
super.dispose();
}
reload(): void {
throw new Error('Method not implemented.');
}
selectAll(): void {
throw new Error('Method not implemented.');
}
copy(): void {
throw new Error('Method not implemented.');
}
paste(): void {
throw new Error('Method not implemented.');
}
cut(): void {
throw new Error('Method not implemented.');
}
undo(): void {
throw new Error('Method not implemented.');
}
redo(): void {
throw new Error('Method not implemented.');
}
showFind(): void {
throw new Error('Method not implemented.');
}
hideFind(): void {
throw new Error('Method not implemented.');
}
private _send(channel: string, data: any): void {
this._ready
.then(() => this.element.contentWindow!.postMessage({
channel: channel,
args: data
}, '*'))
.catch(err => console.error(err));
}
private style(theme: ITheme): void {
const { styles, activeTheme } = getWebviewThemeData(theme, this._configurationService);
this._send('styles', { styles, activeTheme });
}
}
......@@ -3,13 +3,23 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IWebviewService, Webview, WebviewContentOptions, WebviewOptions } from 'vs/workbench/contrib/webview/common/webview';
export class NullWebviewService implements IWebviewService {
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IFrameWebview as WebviewElement } from 'vs/workbench/contrib/webview/browser/webviewElement';
import { IWebviewService, WebviewOptions, WebviewContentOptions, Webview } from 'vs/workbench/contrib/webview/common/webview';
export class WebviewService implements IWebviewService {
_serviceBrand: any;
createWebview(_options: WebviewOptions, _contentOptions: WebviewContentOptions): Webview {
throw new Error('not supported');
constructor(
@IInstantiationService private readonly _instantiationService: IInstantiationService,
) { }
createWebview(
options: WebviewOptions,
contentOptions: WebviewContentOptions
): Webview {
return this._instantiationService.createInstance(WebviewElement,
options,
contentOptions);
}
}
}
\ No newline at end of file
......@@ -40,5 +40,10 @@
document.addEventListener('DOMContentLoaded', () => {
registerVscodeResourceScheme();
// Forward messages from the embedded iframe
window.onmessage = (message) => {
ipcRenderer.sendToHost(message.data.command, message.data.data);
};
});
}());
\ No newline at end of file
......@@ -4,8 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IWebviewService, Webview, WebviewContentOptions, WebviewOptions } from 'vs/workbench/contrib/webview/common/webview';
import { WebviewElement } from 'vs/workbench/contrib/webview/electron-browser/webviewElement';
import { IWebviewService, WebviewOptions, WebviewContentOptions, Webview } from 'vs/workbench/contrib/webview/common/webview';
export class WebviewService implements IWebviewService {
_serviceBrand: any;
......@@ -18,10 +18,8 @@ export class WebviewService implements IWebviewService {
options: WebviewOptions,
contentOptions: WebviewContentOptions
): Webview {
const element = this._instantiationService.createInstance(WebviewElement,
return this._instantiationService.createInstance(WebviewElement,
options,
contentOptions);
return element;
}
}
\ No newline at end of file
......@@ -247,13 +247,12 @@ import 'vs/workbench/contrib/markers/browser/markers.contribution';
import 'vs/workbench/contrib/url/common/url.contribution';
// Webview
// import 'vs/workbench/contrib/webview/browser/webview.contribution';
// import 'vs/workbench/contrib/webview/electron-browser/webview.contribution';
import 'vs/workbench/contrib/webview/browser/webview.contribution';
import { IWebviewService } from 'vs/workbench/contrib/webview/common/webview';
import { NullWebviewService } from 'vs/workbench/contrib/webview/browser/webviewService';
import { WebviewService } from 'vs/workbench/contrib/webview/browser/webviewService';
import { IWebviewEditorService, WebviewEditorService } from 'vs/workbench/contrib/webview/browser/webviewEditorService';
registerSingleton(IWebviewService, NullWebviewService, true);
registerSingleton(IWebviewService, WebviewService, true);
registerSingleton(IWebviewEditorService, WebviewEditorService, true);
// Extensions Management
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册