diff --git a/src/vs/workbench/parts/extensions/browser/extensionEditor.ts b/src/vs/workbench/parts/extensions/browser/extensionEditor.ts index 24f85a5f3f1c831a55e0a81dda2ad7f87e8e6040..cdeb97162a5e8cc00b33b889f937273a0f6fed59 100644 --- a/src/vs/workbench/parts/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/parts/extensions/browser/extensionEditor.ts @@ -319,7 +319,8 @@ export class ExtensionEditor extends BaseEditor { .then(body => { const webview = new WebView( this.content, - document.querySelector('.monaco-editor-background') + document.querySelector('.monaco-editor-background'), + { nodeintegration: false } ); webview.style(this.themeService.getColorTheme()); diff --git a/src/vs/workbench/parts/html/browser/htmlEditorZone.ts b/src/vs/workbench/parts/html/browser/htmlEditorZone.ts index 0827b2d7280d7211a0c9e378923d6522559fa5bd..f75fb67e0785e1d512f2e9965f3a6857a7aaaa71 100644 --- a/src/vs/workbench/parts/html/browser/htmlEditorZone.ts +++ b/src/vs/workbench/parts/html/browser/htmlEditorZone.ts @@ -41,7 +41,7 @@ class HtmlZone implements IViewZone { private _onVisibilityChanged(): void { if (this._domNode.hasAttribute('monaco-visible-view-zone') && !this._webview) { - this._webview = new Webview(this.domNode, document.querySelector('.monaco-editor-background')); + this._webview = new Webview(this.domNode, document.querySelector('.monaco-editor-background'), { nodeintegration: true }); this._disposables.push(this._webview); this._webview.contents = [this.htmlContent]; } diff --git a/src/vs/workbench/parts/html/browser/htmlPreviewPart.ts b/src/vs/workbench/parts/html/browser/htmlPreviewPart.ts index ffea4d6a3371e8be472c378628808609ba46d551..85810a8ec944ff501900e62fa7316c948d01abee 100644 --- a/src/vs/workbench/parts/html/browser/htmlPreviewPart.ts +++ b/src/vs/workbench/parts/html/browser/htmlPreviewPart.ts @@ -77,7 +77,7 @@ export class HtmlPreviewPart extends BaseEditor { private get webview(): Webview { if (!this._webview) { - this._webview = new Webview(this._container, document.querySelector('.monaco-editor-background')); + this._webview = new Webview(this._container, document.querySelector('.monaco-editor-background'), { nodeintegration: true }); this._webview.baseUrl = this._baseUrl && this._baseUrl.toString(true); this._webviewDisposables = [ diff --git a/src/vs/workbench/parts/html/browser/webview-pre.js b/src/vs/workbench/parts/html/browser/webview-pre.js new file mode 100644 index 0000000000000000000000000000000000000000..31972a90ea1ef04bd5148f2e8947242c23ba7af9 --- /dev/null +++ b/src/vs/workbench/parts/html/browser/webview-pre.js @@ -0,0 +1,139 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +var initData = {}; + +function styleBody(body) { + if (!body) { + return + } + body.classList.remove('vscode-light', 'vscode-dark', 'vscode-high-contrast'); + body.classList.add(initData.activeTheme); +}; + +function getTarget() { + return document.getElementById('_target'); +}; + +const ipcRenderer = require('electron').ipcRenderer; + +document.addEventListener("DOMContentLoaded", function (event) { + ipcRenderer.on('baseUrl', function (event, value) { + initData.baseUrl = value; + }); + + ipcRenderer.on('styles', function (event, value, activeTheme) { + initData.styles = value; + initData.activeTheme = activeTheme; + + // webview + let defaultStyles = document.getElementById('_defaultStyles'); + defaultStyles.innerHTML = initData.styles; + + let body = getTarget().contentDocument.getElementsByTagName('body'); + styleBody(body[0]); + + // iframe + defaultStyles = getTarget().contentDocument.getElementById('_defaultStyles'); + if (defaultStyles) { + defaultStyles.innerHTML = initData.styles; + } + }); + + // propagate focus + ipcRenderer.on('focus', function () { + getTarget().contentWindow.focus(); + }); + + // update iframe-contents + ipcRenderer.on('content', function (event, value) { + const text = value.join('\n'); + const newDocument = new DOMParser().parseFromString(text, 'text/html'); + + // know what happens here + const stats = { + scriptTags: newDocument.documentElement.querySelectorAll('script').length, + inputTags: newDocument.documentElement.querySelectorAll('input').length, + styleTags: newDocument.documentElement.querySelectorAll('style').length, + linkStyleSheetTags: newDocument.documentElement.querySelectorAll('link[rel=stylesheet]').length, + stringLen: text.length + }; + + // set base-url if applicable + if (initData.baseUrl && newDocument.head.getElementsByTagName('base').length === 0) { + const baseElement = document.createElement('base'); + baseElement.href = initData.baseUrl; + newDocument.head.appendChild(baseElement); + } + + // apply default styles + const defaultStyles = newDocument.createElement('style'); + defaultStyles.id = '_defaultStyles'; + defaultStyles.innerHTML = initData.styles; + if (newDocument.head.hasChildNodes()) { + newDocument.head.insertBefore(defaultStyles, newDocument.head.firstChild); + } else { + newDocument.head.appendChild(defaultStyles); + } + + // script to bubble out link-clicks + const defaultScripts = newDocument.createElement('script'); + defaultScripts.innerHTML = ` + document.body.addEventListener('click', function(event) { + let node = event.target; + while (node) { + if (node.tagName === 'A' && node.href) { + let baseElement = window.document.getElementsByTagName('base')[0]; + if (baseElement && node.href.indexOf(baseElement.href) >= 0 && node.hash) { + let scrollTarget = window.document.getElementById(node.hash.substr(1, node.hash.length - 1)); + if (scrollTarget) { + scrollgetTarget().scrollIntoView(); + } + } else { + window.parent.postMessage({ command: 'did-click-link', data: node.href }, 'file://'); + } + event.preventDefault(); + break; + } + node = node.parentNode; + } + });` + + + newDocument.body.appendChild(defaultScripts); + styleBody(newDocument.body); + + // keep current scrollTop around and use later + const {scrollTop} = getTarget().contentDocument.body; + + // write new content onto iframe + getTarget().contentDocument.open('text/html', 'replace'); + // 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 + getTarget().contentDocument.write(''); + getTarget().contentDocument.write(newDocument.documentElement.innerHTML); + getTarget().contentDocument.close(); + + // workaround for https://github.com/Microsoft/vscode/issues/12865 + // check new scrollTop and reset if neccessary + setTimeout(() => { + if (scrollTop !== getTarget().contentDocument.body.scrollTop) { + getTarget().contentDocument.body.scrollTop = scrollTop; + } + }, 0); + + ipcRenderer.sendToHost('did-set-content', stats); + }); + + // forward messages from the embedded iframe + window.onmessage = function (message) { + const { command, data} = message.data; + ipcRenderer.sendToHost(command, data); + }; + + // signal ready + ipcRenderer.sendToHost('webview-ready', process.pid); +}); \ No newline at end of file diff --git a/src/vs/workbench/parts/html/browser/webview.html b/src/vs/workbench/parts/html/browser/webview.html index 5e16b18eb4cdf05253ae37ce5335d7364f7dba37..e17077170e74ba72a59ff427b77420eb9558c8be 100644 --- a/src/vs/workbench/parts/html/browser/webview.html +++ b/src/vs/workbench/parts/html/browser/webview.html @@ -6,138 +6,5 @@ - \ No newline at end of file diff --git a/src/vs/workbench/parts/html/browser/webview.ts b/src/vs/workbench/parts/html/browser/webview.ts index 19b225453da29f92326505dc63542b1564fce337..9e1bb56e65186c4cfba1b202468f716ef1bd22e1 100644 --- a/src/vs/workbench/parts/html/browser/webview.ts +++ b/src/vs/workbench/parts/html/browser/webview.ts @@ -20,6 +20,7 @@ declare interface WebviewElement extends HTMLElement { autoSize: 'on'; nodeintegration: 'on'; disablewebsecurity: 'on'; + preload: string; getURL(): string; getTitle(): string; @@ -47,6 +48,10 @@ MenuRegistry.addCommand({ type ApiThemeClassName = 'vscode-light' | 'vscode-dark' | 'vscode-high-contrast'; +export interface WebviewOptions { + nodeintegration: boolean; +} + export default class Webview { private _webview: WebviewElement; @@ -55,7 +60,7 @@ export default class Webview { private _onDidClickLink = new Emitter(); private _onDidLoadContent = new Emitter<{ stats: any }>(); - constructor(parent: HTMLElement, private _styleElement: Element) { + constructor(parent: HTMLElement, private _styleElement: Element, options: WebviewOptions) { this._webview = document.createElement('webview'); this._webview.style.width = '100%'; @@ -63,7 +68,11 @@ export default class Webview { this._webview.style.outline = '0'; this._webview.style.opacity = '0'; this._webview.autoSize = 'on'; - this._webview.nodeintegration = 'on'; + if (options.nodeintegration) { + this._webview.nodeintegration = 'on'; + } + + this._webview.preload = require.toUrl('./webview-pre.js'); this._webview.src = require.toUrl('./webview.html'); this._ready = new TPromise(resolve => { diff --git a/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts b/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts index 757299b8b27e41fcd7e76d13468337dbd48a7748..40c3a86214e9d09701107993480699e602b81111 100644 --- a/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts +++ b/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts @@ -91,7 +91,8 @@ export class ReleaseNotesEditor extends BaseEditor { .then(body => { this.webview = new WebView( this.content, - document.querySelector('.monaco-editor-background') + document.querySelector('.monaco-editor-background'), + { nodeintegration: false } ); this.webview.baseUrl = `https://code.visualstudio.com/raw/`;