webview.ts 7.9 KB
Newer Older
1 2 3 4 5 6 7
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

'use strict';

8
import { localize } from 'vs/nls';
9
import URI from 'vs/base/common/uri';
J
Johannes Rieken 已提交
10 11 12 13 14
import { TPromise } from 'vs/base/common/winjs.base';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import Event, { Emitter } from 'vs/base/common/event';
import { addDisposableListener, addClass } from 'vs/base/browser/dom';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
15
import { MenuRegistry } from 'vs/platform/actions/common/actions';
16
import { editorBackground, editorForeground } from 'vs/platform/theme/common/colorRegistry';
17
import { ITheme, LIGHT, DARK } from 'vs/platform/theme/common/themeService';
18 19 20 21

declare interface WebviewElement extends HTMLElement {
	src: string;
	autoSize: 'on';
M
Matt Bierner 已提交
22
	preload: string;
23
	contextIsolation: boolean;
24 25 26 27
	send(channel: string, ...args: any[]);
	openDevTools(): any;
}

28 29 30 31 32 33 34
CommandsRegistry.registerCommand('_webview.openDevTools', function () {
	const elements = document.querySelectorAll('webview.ready');
	for (let i = 0; i < elements.length; i++) {
		try {
			(<WebviewElement>elements.item(i)).openDevTools();
		} catch (e) {
			console.error(e);
35
		}
36 37 38 39 40 41 42
	}
});

MenuRegistry.addCommand({
	id: '_webview.openDevTools',
	title: localize('devtools.webview', "Developer: Webview Tools")
});
43

44
type ApiThemeClassName = 'vscode-light' | 'vscode-dark' | 'vscode-high-contrast';
K
kieferrm 已提交
45

M
Matt Bierner 已提交
46 47 48 49
export interface WebviewOptions {
	enableJavascript?: boolean;
}

50 51 52 53 54
export default class Webview {

	private _webview: WebviewElement;
	private _ready: TPromise<this>;
	private _disposables: IDisposable[];
55 56
	private _onDidClickLink = new Emitter<URI>();
	private _onDidLoadContent = new Emitter<{ stats: any }>();
57

58 59
	private _onDidScroll = new Emitter<{ scrollYPercentage: number }>();

60 61
	constructor(
		private parent: HTMLElement,
M
Matt Bierner 已提交
62
		private _styleElement: Element,
63
		private options: WebviewOptions = {}
64
	) {
B
Benjamin Pasero 已提交
65
		this._webview = <any>document.createElement('webview');
66 67 68

		this._webview.style.width = '100%';
		this._webview.style.height = '100%';
69
		this._webview.style.outline = '0';
70
		this._webview.style.opacity = '0';
71
		this._webview.contextIsolation = true;
M
Matt Bierner 已提交
72

73 74 75
		// disable auxclick events (see https://developers.google.com/web/updates/2016/10/auxclick)
		this._webview.setAttribute('disableblinkfeatures', 'Auxclick');

76
		this._webview.setAttribute('disableguestresize', '');
77
		this._webview.setAttribute('webpreferences', 'contextIsolation=yes');
78
		this._webview.setAttribute('partition', 'webview');
79

M
Matt Bierner 已提交
80
		this._webview.preload = require.toUrl('./webview-pre.js');
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
		this._webview.src = require.toUrl('./webview.html');

		this._ready = new TPromise<this>(resolve => {
			const subscription = addDisposableListener(this._webview, 'ipc-message', (event) => {
				if (event.channel === 'webview-ready') {
					// console.info('[PID Webview] ' + event.args[0]);
					addClass(this._webview, 'ready'); // can be found by debug command

					subscription.dispose();
					resolve(this);
				}
			});
		});

		this._disposables = [
			addDisposableListener(this._webview, 'console-message', function (e: { level: number; message: string; line: number; sourceId: string; }) {
				console.log(`[Embedded Page] ${e.message}`);
			}),
99 100 101
			addDisposableListener(this._webview, 'dom-ready', () => {
				this.layout();
			}),
M
Matt Bierner 已提交
102
			addDisposableListener(this._webview, 'crashed', () => {
103 104 105 106 107
				console.error('embedded page crashed');
			}),
			addDisposableListener(this._webview, 'ipc-message', (event) => {
				if (event.channel === 'did-click-link') {
					let [uri] = event.args;
108
					this._onDidClickLink.fire(URI.parse(uri));
109 110 111 112 113
					return;
				}

				if (event.channel === 'did-set-content') {
					this._webview.style.opacity = '';
114 115
					let [stats] = event.args;
					this._onDidLoadContent.fire({ stats });
116
					this.layout();
117
					return;
118
				}
119 120 121 122 123 124 125

				if (event.channel === 'did-scroll') {
					if (event.args && typeof event.args[0] === 'number') {
						this._onDidScroll.fire({ scrollYPercentage: event.args[0] });
					}
					return;
				}
126 127 128
			})
		];

J
Johannes Rieken 已提交
129 130 131
		if (parent) {
			parent.appendChild(this._webview);
		}
132 133 134
	}

	dispose(): void {
135 136
		this._onDidClickLink.dispose();
		this._onDidLoadContent.dispose();
137
		this._disposables = dispose(this._disposables);
J
Joao Moreno 已提交
138 139 140 141

		if (this._webview.parentElement) {
			this._webview.parentElement.removeChild(this._webview);
		}
142 143
	}

144 145 146 147 148 149 150 151
	get onDidClickLink(): Event<URI> {
		return this._onDidClickLink.event;
	}

	get onDidLoadContent(): Event<{ stats: any }> {
		return this._onDidLoadContent.event;
	}

152 153 154 155
	get onDidScroll(): Event<{ scrollYPercentage: number }> {
		return this._onDidScroll.event;
	}

156 157 158 159 160 161
	private _send(channel: string, ...args: any[]): void {
		this._ready
			.then(() => this._webview.send(channel, ...args))
			.done(void 0, console.error);
	}

162 163 164 165
	set initialScrollProgress(value: number) {
		this._send('initial-scroll-position', value);
	}

166
	set contents(value: string[]) {
M
Matt Bierner 已提交
167 168
		this._send('content', {
			contents: value,
169
			options: this.options
M
Matt Bierner 已提交
170
		});
171 172 173 174 175 176 177
	}

	set baseUrl(value: string) {
		this._send('baseUrl', value);
	}

	focus(): void {
178
		this._webview.focus();
179 180 181
		this._send('focus');
	}

182 183 184 185
	public sendMessage(data: any): void {
		this._send('message', data);
	}

186
	style(theme: ITheme): void {
187
		const { fontFamily, fontWeight, fontSize } = window.getComputedStyle(this._styleElement); // TODO@theme avoid styleElement
188

K
kieferrm 已提交
189
		let value = `
190
		:root {
191 192
			--background-color: ${theme.getColor(editorBackground)};
			--color: ${theme.getColor(editorForeground)};
193
			--font-family: ${fontFamily};
194
			--font-weight: ${fontWeight};
195 196 197 198 199 200
			--font-size: ${fontSize};
		}
		body {
			background-color: var(--background-color);
			color: var(--color);
			font-family: var(--font-family);
201
			font-weight: var(--font-weight);
202
			font-size: var(--font-size);
K
kieferrm 已提交
203
			margin: 0;
204
			padding: 0 20px;
205
		}
K
kieferrm 已提交
206

207 208 209 210 211 212 213 214 215 216 217 218
		img {
			max-width: 100%;
			max-height: 100%;
		}
		a:focus,
		input:focus,
		select:focus,
		textarea:focus {
			outline: 1px solid -webkit-focus-ring-color;
			outline-offset: -1px;
		}
		::-webkit-scrollbar {
J
Joao Moreno 已提交
219
			width: 10px;
220 221 222
			height: 10px;
		}`;

223 224

		let activeTheme: ApiThemeClassName;
K
kieferrm 已提交
225

226
		if (theme.type === LIGHT) {
227 228 229 230
			value += `
			::-webkit-scrollbar-thumb {
				background-color: rgba(100, 100, 100, 0.4);
			}
K
kieferrm 已提交
231 232 233
			::-webkit-scrollbar-thumb:hover {
				background-color: rgba(100, 100, 100, 0.7);
			}
234 235 236
			::-webkit-scrollbar-thumb:active {
				background-color: rgba(0, 0, 0, 0.6);
			}`;
K
kieferrm 已提交
237

238
			activeTheme = 'vscode-light';
K
kieferrm 已提交
239

240
		} else if (theme.type === DARK) {
241 242 243 244
			value += `
			::-webkit-scrollbar-thumb {
				background-color: rgba(121, 121, 121, 0.4);
			}
K
kieferrm 已提交
245 246 247
			::-webkit-scrollbar-thumb:hover {
				background-color: rgba(100, 100, 100, 0.7);
			}
248 249 250
			::-webkit-scrollbar-thumb:active {
				background-color: rgba(85, 85, 85, 0.8);
			}`;
K
kieferrm 已提交
251

252
			activeTheme = 'vscode-dark';
K
kieferrm 已提交
253 254 255 256 257

		} else {
			value += `
			::-webkit-scrollbar-thumb {
				background-color: rgba(111, 195, 223, 0.3);
K
kieferrm 已提交
258
			}
K
kieferrm 已提交
259 260 261 262 263 264 265
			::-webkit-scrollbar-thumb:hover {
				background-color: rgba(111, 195, 223, 0.8);
			}
			::-webkit-scrollbar-thumb:active {
				background-color: rgba(111, 195, 223, 0.8);
			}`;

266
			activeTheme = 'vscode-high-contrast';
267 268
		}

269
		this._send('styles', value, activeTheme);
270
	}
271 272 273 274 275 276

	public layout(): void {
		const contents = (this._webview as any).getWebContents();
		if (!contents) {
			return;
		}
277
		const window = contents.getOwnerBrowserWindow();
M
Matt Bierner 已提交
278
		if (!window || !window.webContents || window.webContents.isDestroyed()) {
279 280 281
			return;
		}
		window.webContents.getZoomFactor(factor => {
282 283 284 285
			if (contents.isDestroyed()) {
				return;
			}

286
			contents.setZoomFactor(factor);
287

288 289
			const width = this.parent.clientWidth;
			const height = this.parent.clientHeight;
290 291 292 293 294 295 296 297
			contents.setSize({
				normal: {
					width: Math.floor(width * factor),
					height: Math.floor(height * factor)
				}
			});
		});
	}
298
}