main.js 14.0 KB
Newer Older
M
Matt Bierner 已提交
1 2 3 4
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
5
// @ts-check
M
Matt Bierner 已提交
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
(function () {
	'use strict';

	/**
	 * Use polling to track focus of main webview and iframes within the webview
	 *
	 * @param {Object} handlers
	 * @param {() => void} handlers.onFocus
	 * @param {() => void} handlers.onBlur
	 */
	const trackFocus = ({ onFocus, onBlur }) => {
		const interval = 50;
		let isFocused = document.hasFocus();
		setInterval(() => {
			const isCurrentlyFocused = document.hasFocus();
			if (isCurrentlyFocused === isFocused) {
				return;
			}
			isFocused = isCurrentlyFocused;
			if (isCurrentlyFocused) {
				onFocus();
			} else {
				onBlur();
			}
		}, interval);
	};

	const getActiveFrame = () => {
		return /** @type {HTMLIFrameElement} */ (document.getElementById('active-frame'));
	};

	const getPendingFrame = () => {
		return /** @type {HTMLIFrameElement} */ (document.getElementById('pending-frame'));
	};

	const defaultCssRules = `
42 43 44
	body {
		background-color: var(--vscode-editor-background);
		color: var(--vscode-editor-foreground);
45 46 47
		font-family: var(--vscode-font-family);
		font-weight: var(--vscode-font-weight);
		font-size: var(--vscode-font-size);
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
		margin: 0;
		padding: 0 20px;
	}

	img {
		max-width: 100%;
		max-height: 100%;
	}

	a {
		color: var(--vscode-textLink-foreground);
	}

	a:hover {
		color: var(--vscode-textLink-activeForeground);
	}

	a:focus,
	input:focus,
	select:focus,
	textarea:focus {
		outline: 1px solid -webkit-focus-ring-color;
		outline-offset: -1px;
	}

	code {
		color: var(--vscode-textPreformat-foreground);
	}

	blockquote {
		background: var(--vscode-textBlockQuote-background);
		border-color: var(--vscode-textBlockQuote-border);
	}

	::-webkit-scrollbar {
		width: 10px;
		height: 10px;
	}

	::-webkit-scrollbar-thumb {
		background-color: var(--vscode-scrollbarSlider-background);
	}
	::-webkit-scrollbar-thumb:hover {
		background-color: var(--vscode-scrollbarSlider-hoverBackground);
	}
	::-webkit-scrollbar-thumb:active {
		background-color: var(--vscode-scrollbarSlider-activeBackground);
	}`;

M
Matt Bierner 已提交
97
	/**
M
Matt Bierner 已提交
98 99 100 101 102 103
	 * @typedef {{
	 *   postMessage: (channel: string, data?: any) => void,
	 *   onMessage: (channel: string, handler: any) => void,
	 *   injectHtml?: (document: HTMLDocument) => void,
	 *   focusIframeOnCreate?: boolean
	 * }} HostCommunications
M
Matt Bierner 已提交
104
	 */
105

106
	/**
M
Matt Bierner 已提交
107
	 * @param {HostCommunications} host
108
	 */
M
Matt Bierner 已提交
109 110 111 112 113 114 115 116 117 118 119
	function createWebviewManager(host) {
		// state
		let firstLoad = true;
		let loadTimeout;
		let pendingMessages = [];
		let isInDevelopmentMode = false;

		const initData = {
			initialScrollProgress: undefined
		};

120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
		// Service worker for resource loading
		const FAKE_LOAD = !!navigator.serviceWorker;
		if (navigator.serviceWorker) {
			navigator.serviceWorker.register('service-worker.js');

			navigator.serviceWorker.ready.then(registration => {
				host.onMessage('loaded-resource', event => {
					registration.active.postMessage({ channel: 'loaded-resource', data: event.data.args });
				});
			});

			navigator.serviceWorker.addEventListener('message', event => {
				switch (event.data.channel) {
					case 'load-resource':
						host.postMessage('load-resource', { path: event.data.path });
						return;
				}
			});
		}

M
Matt Bierner 已提交
140 141 142 143 144 145 146
		/**
		 * @param {HTMLDocument?} document
		 * @param {HTMLElement?} body
		 */
		const applyStyles = (document, body) => {
			if (!document) {
				return;
147
			}
M
Matt Bierner 已提交
148

M
Matt Bierner 已提交
149 150 151 152
			if (body) {
				body.classList.remove('vscode-light', 'vscode-dark', 'vscode-high-contrast');
				body.classList.add(initData.activeTheme);
			}
M
Matt Bierner 已提交
153

M
Matt Bierner 已提交
154 155 156 157
			if (initData.styles) {
				for (const variable of Object.keys(initData.styles)) {
					document.documentElement.style.setProperty(`--${variable}`, initData.styles[variable]);
				}
158
			}
M
Matt Bierner 已提交
159
		};
M
Matt Bierner 已提交
160

M
Matt Bierner 已提交
161 162 163 164 165 166 167
		/**
		 * @param {MouseEvent} event
		 */
		const handleInnerClick = (event) => {
			if (!event || !event.view || !event.view.document) {
				return;
			}
168

M
Matt Bierner 已提交
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
			let baseElement = event.view.document.getElementsByTagName('base')[0];
			/** @type {any} */
			let node = event.target;
			while (node) {
				if (node.tagName && node.tagName.toLowerCase() === 'a' && node.href) {
					if (node.getAttribute('href') === '#') {
						event.view.scrollTo(0, 0);
					} else if (node.hash && (node.getAttribute('href') === node.hash || (baseElement && node.href.indexOf(baseElement.href) >= 0))) {
						let scrollTarget = event.view.document.getElementById(node.hash.substr(1, node.hash.length - 1));
						if (scrollTarget) {
							scrollTarget.scrollIntoView();
						}
					} else {
						host.postMessage('did-click-link', node.href.baseVal || node.href);
					}
					event.preventDefault();
					break;
				}
				node = node.parentNode;
			}
		};

		/**
		 * @param {KeyboardEvent} e
		 */
		const handleInnerKeydown = (e) => {
			host.postMessage('did-keydown', {
				key: e.key,
				keyCode: e.keyCode,
				code: e.code,
				shiftKey: e.shiftKey,
				altKey: e.altKey,
				ctrlKey: e.ctrlKey,
				metaKey: e.metaKey,
				repeat: e.repeat
			});
		};
M
Matt Bierner 已提交
206

M
Matt Bierner 已提交
207 208 209 210 211 212
		let isHandlingScroll = false;
		const handleInnerScroll = (event) => {
			if (!event.target || !event.target.body) {
				return;
			}
			if (isHandlingScroll) {
213 214
				return;
			}
M
Matt Bierner 已提交
215

M
Matt Bierner 已提交
216 217 218
			const progress = event.currentTarget.scrollY / event.target.body.clientHeight;
			if (isNaN(progress)) {
				return;
219
			}
M
Matt Bierner 已提交
220

M
Matt Bierner 已提交
221 222 223 224 225 226 227 228 229 230 231 232
			isHandlingScroll = true;
			window.requestAnimationFrame(() => {
				try {
					host.postMessage('did-scroll', progress);
				} catch (e) {
					// noop
				}
				isHandlingScroll = false;
			});
		};

		document.addEventListener('DOMContentLoaded', () => {
233 234
			const ID = document.location.search.match(/\bid=([\w-]+)/)[1];

M
Matt Bierner 已提交
235 236
			if (!document.body) {
				return;
237
			}
M
Matt Bierner 已提交
238

M
Matt Bierner 已提交
239 240 241
			host.onMessage('styles', (_event, data) => {
				initData.styles = data.styles;
				initData.activeTheme = data.activeTheme;
M
Matt Bierner 已提交
242

M
Matt Bierner 已提交
243 244 245 246
				const target = getActiveFrame();
				if (!target) {
					return;
				}
247

M
Matt Bierner 已提交
248 249
				if (target.contentDocument) {
					applyStyles(target.contentDocument, target.contentDocument.body);
250 251 252
				}
			});

M
Matt Bierner 已提交
253 254 255 256 257 258 259 260 261 262 263 264
			// propagate focus
			host.onMessage('focus', () => {
				const target = getActiveFrame();
				if (target) {
					target.contentWindow.focus();
				}
			});

			// update iframe-contents
			host.onMessage('content', (_event, data) => {
				const options = data.options;

265
				const text = data.contents;
M
Matt Bierner 已提交
266 267 268 269 270 271 272 273 274 275 276 277
				const newDocument = new DOMParser().parseFromString(text, 'text/html');

				newDocument.querySelectorAll('a').forEach(a => {
					if (!a.title) {
						a.title = a.getAttribute('href');
					}
				});

				// apply default script
				if (options.allowScripts) {
					const defaultScript = newDocument.createElement('script');
					defaultScript.textContent = `
278
					const acquireVsCodeApi = (function() {
M
Matt Bierner 已提交
279
						const originalPostMessage = window.parent.postMessage.bind(window.parent);
280
						const targetOrigin = '*';
281 282
						let acquired = false;

283 284
						let state = ${data.state ? `JSON.parse(${JSON.stringify(data.state)})` : undefined};

285 286 287
						return () => {
							if (acquired) {
								throw new Error('An instance of the VS Code API has already been acquired');
M
Matt Bierner 已提交
288
							}
289 290 291
							acquired = true;
							return Object.freeze({
								postMessage: function(msg) {
292
									return originalPostMessage({ command: 'onmessage', data: msg }, targetOrigin);
293
								},
294 295
								setState: function(newState) {
									state = newState;
296
									originalPostMessage({ command: 'do-update-state', data: JSON.stringify(newState) }, targetOrigin);
297
									return newState;
298 299 300
								},
								getState: function() {
									return state;
301 302
								}
							});
M
Matt Bierner 已提交
303
						};
304
					})();
M
Matt Bierner 已提交
305
					delete window.parent;
306 307
					delete window.top;
					delete window.frameElement;
M
Matt Bierner 已提交
308 309
				`;

M
Matt Bierner 已提交
310 311
					newDocument.head.prepend(defaultScript);
				}
M
Matt Bierner 已提交
312

M
Matt Bierner 已提交
313 314 315 316 317
				// apply default styles
				const defaultStyles = newDocument.createElement('style');
				defaultStyles.id = '_defaultStyles';
				defaultStyles.innerHTML = defaultCssRules;
				newDocument.head.prepend(defaultStyles);
318

M
Matt Bierner 已提交
319
				applyStyles(newDocument, newDocument.body);
M
Matt Bierner 已提交
320

M
Matt Bierner 已提交
321 322 323
				if (host.injectHtml) {
					host.injectHtml(newDocument);
				}
324

M
Matt Bierner 已提交
325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340
				const frame = getActiveFrame();
				const wasFirstLoad = firstLoad;
				// keep current scrollY around and use later
				let setInitialScrollPosition;
				if (firstLoad) {
					firstLoad = false;
					setInitialScrollPosition = (body, window) => {
						if (!isNaN(initData.initialScrollProgress)) {
							if (window.scrollY === 0) {
								window.scroll(0, body.clientHeight * initData.initialScrollProgress);
							}
						}
					};
				} else {
					const scrollY = frame && frame.contentDocument && frame.contentDocument.body ? frame.contentWindow.scrollY : 0;
					setInitialScrollPosition = (body, window) => {
341
						if (window.scrollY === 0) {
M
Matt Bierner 已提交
342
							window.scroll(0, scrollY);
343
						}
M
Matt Bierner 已提交
344 345
					};
				}
346

M
Matt Bierner 已提交
347 348 349 350 351 352 353 354 355
				// Clean up old pending frames and set current one as new one
				const previousPendingFrame = getPendingFrame();
				if (previousPendingFrame) {
					previousPendingFrame.setAttribute('id', '');
					document.body.removeChild(previousPendingFrame);
				}
				if (!wasFirstLoad) {
					pendingMessages = [];
				}
356

M
Matt Bierner 已提交
357 358 359 360
				const newFrame = document.createElement('iframe');
				newFrame.setAttribute('id', 'pending-frame');
				newFrame.setAttribute('frameborder', '0');
				newFrame.setAttribute('sandbox', options.allowScripts ? 'allow-scripts allow-forms allow-same-origin' : 'allow-same-origin');
361 362 363 364 365
				if (FAKE_LOAD) {
					// We should just be able to use srcdoc, but I wasn't
					// 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.
366
					newFrame.src = `/fake.html?id=${ID}`;
367
				}
M
Matt Bierner 已提交
368 369
				newFrame.style.cssText = 'display: block; margin: 0; overflow: hidden; position: absolute; width: 100%; height: 100%; visibility: hidden';
				document.body.appendChild(newFrame);
370

371 372 373 374
				if (!FAKE_LOAD) {
					// write new content onto iframe
					newFrame.contentDocument.open();
				}
375

M
Matt Bierner 已提交
376
				newFrame.contentWindow.addEventListener('keydown', handleInnerKeydown);
377

M
Matt Bierner 已提交
378
				newFrame.contentWindow.addEventListener('DOMContentLoaded', e => {
379 380 381 382 383
					if (FAKE_LOAD) {
						newFrame.contentDocument.open();
						newFrame.contentDocument.write('<!DOCTYPE html>');
						newFrame.contentDocument.write(newDocument.documentElement.innerHTML);
						newFrame.contentDocument.close();
384
						hookupOnLoadHandlers(newFrame);
385
					}
M
Matt Bierner 已提交
386 387 388 389 390
					const contentDocument = e.target ? (/** @type {HTMLDocument} */ (e.target)) : undefined;
					if (contentDocument) {
						applyStyles(contentDocument, contentDocument.body);
					}
				});
391

M
Matt Bierner 已提交
392 393 394 395 396
				const onLoad = (contentDocument, contentWindow) => {
					if (contentDocument && contentDocument.body) {
						// Workaround for https://github.com/Microsoft/vscode/issues/12865
						// check new scrollY and reset if neccessary
						setInitialScrollPosition(contentDocument.body, contentWindow);
397
					}
398

M
Matt Bierner 已提交
399 400 401 402 403 404 405 406 407 408 409 410 411
					const newFrame = getPendingFrame();
					if (newFrame && newFrame.contentDocument && newFrame.contentDocument === contentDocument) {
						const oldActiveFrame = getActiveFrame();
						if (oldActiveFrame) {
							document.body.removeChild(oldActiveFrame);
						}
						// Styles may have changed since we created the element. Make sure we re-style
						applyStyles(newFrame.contentDocument, newFrame.contentDocument.body);
						newFrame.setAttribute('id', 'active-frame');
						newFrame.style.visibility = 'visible';
						if (host.focusIframeOnCreate) {
							newFrame.contentWindow.focus();
						}
412

M
Matt Bierner 已提交
413 414 415 416 417 418 419 420
						contentWindow.addEventListener('scroll', handleInnerScroll);

						pendingMessages.forEach((data) => {
							contentWindow.postMessage(data, '*');
						});
						pendingMessages = [];
					}
				};
421

422
				function hookupOnLoadHandlers(newFrame) {
423 424
					clearTimeout(loadTimeout);
					loadTimeout = undefined;
425
					loadTimeout = setTimeout(() => {
M
Matt Bierner 已提交
426 427
						clearTimeout(loadTimeout);
						loadTimeout = undefined;
428 429 430 431 432 433 434 435 436 437
						onLoad(newFrame.contentDocument, newFrame.contentWindow);
					}, 200);

					newFrame.contentWindow.addEventListener('load', function (e) {
						if (loadTimeout) {
							clearTimeout(loadTimeout);
							loadTimeout = undefined;
							onLoad(e.target, this);
						}
					});
438

439 440 441 442 443 444
					if (!FAKE_LOAD) {
						newFrame.contentWindow.onbeforeunload = () => {
							if (isInDevelopmentMode) { // Allow reloads while developing a webview
								host.postMessage('do-reload');
								return false;
							}
445

446 447 448 449 450
							// Block navigation when not in development mode
							console.log('prevented webview navigation');
							return false;
						};
					}
451 452 453 454 455 456 457 458

					// Bubble out link clicks
					newFrame.contentWindow.addEventListener('click', handleInnerClick);
				}

				if (!FAKE_LOAD) {
					hookupOnLoadHandlers(newFrame);
				}
459

M
Matt Bierner 已提交
460 461
				// set DOCTYPE for newDocument explicitly as DOMParser.parseFromString strips it off
				// and DOCTYPE is needed in the iframe to ensure that the user agent stylesheet is correctly overridden
462 463 464 465 466
				if (!FAKE_LOAD) {
					newFrame.contentDocument.write('<!DOCTYPE html>');
					newFrame.contentDocument.write(newDocument.documentElement.innerHTML);
					newFrame.contentDocument.close();
				}
467

M
Matt Bierner 已提交
468 469
				host.postMessage('did-set-content', undefined);
			});
M
Matt Bierner 已提交
470

M
Matt Bierner 已提交
471 472 473 474 475 476 477 478 479
			// Forward message to the embedded iframe
			host.onMessage('message', (_event, data) => {
				const pending = getPendingFrame();
				if (!pending) {
					const target = getActiveFrame();
					if (target) {
						target.contentWindow.postMessage(data, '*');
						return;
					}
480
				}
M
Matt Bierner 已提交
481 482
				pendingMessages.push(data);
			});
483

M
Matt Bierner 已提交
484 485 486
			host.onMessage('initial-scroll-position', (_event, progress) => {
				initData.initialScrollProgress = progress;
			});
487

M
Matt Bierner 已提交
488 489 490
			host.onMessage('devtools-opened', () => {
				isInDevelopmentMode = true;
			});
491

M
Matt Bierner 已提交
492 493 494 495
			trackFocus({
				onFocus: () => host.postMessage('did-focus'),
				onBlur: () => host.postMessage('did-blur')
			});
M
Matt Bierner 已提交
496

M
Matt Bierner 已提交
497 498 499 500
			// signal ready
			host.postMessage('webview-ready', {});
		});
	}
501

M
Matt Bierner 已提交
502 503 504 505 506 507
	if (typeof module !== 'undefined') {
		module.exports = createWebviewManager;
	} else {
		window.createWebviewManager = createWebviewManager;
	}
}());