Extracting common webview elements

Minimizing diff with the iframe based webview branch
This commit is contained in:
Matt Bierner 2019-06-11 22:37:58 -07:00
parent 39947c7250
commit 8de74d9255
8 changed files with 134 additions and 88 deletions

View file

@ -197,17 +197,17 @@ export class MarkdownContentProvider {
private getCspForResource(resource: vscode.Uri, nonce: string): string {
switch (this.cspArbiter.getSecurityLevelForResource(resource)) {
case MarkdownPreviewSecurityLevel.AllowInsecureContent:
return `<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src vscode-resource: http: https: data:; media-src vscode-resource: http: https: data:; script-src 'nonce-${nonce}'; style-src vscode-resource: 'unsafe-inline' http: https: data:; font-src vscode-resource: http: https: data:;">`;
return `<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src vscode-resource: http: https: data:; media-src vscode-resource: http: https: data:; script-src 'nonce-${nonce}'; style-src 'self' vscode-resource: 'unsafe-inline' http: https: data:; font-src vscode-resource: http: https: data:;">`;
case MarkdownPreviewSecurityLevel.AllowInsecureLocalContent:
return `<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src vscode-resource: https: data: http://localhost:* http://127.0.0.1:*; media-src vscode-resource: https: data: http://localhost:* http://127.0.0.1:*; script-src 'nonce-${nonce}'; style-src vscode-resource: 'unsafe-inline' https: data: http://localhost:* http://127.0.0.1:*; font-src vscode-resource: https: data: http://localhost:* http://127.0.0.1:*;">`;
return `<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src vscode-resource: https: data: http://localhost:* http://127.0.0.1:*; media-src vscode-resource: https: data: http://localhost:* http://127.0.0.1:*; script-src 'nonce-${nonce}'; style-src 'self' vscode-resource: 'unsafe-inline' https: data: http://localhost:* http://127.0.0.1:*; font-src vscode-resource: https: data: http://localhost:* http://127.0.0.1:*;">`;
case MarkdownPreviewSecurityLevel.AllowScriptsAndAllContent:
return '';
case MarkdownPreviewSecurityLevel.Strict:
default:
return `<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src vscode-resource: https: data:; media-src vscode-resource: https: data:; script-src 'nonce-${nonce}'; style-src vscode-resource: 'unsafe-inline' https: data:; font-src vscode-resource: https: data:;">`;
return `<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src vscode-resource: https: data:; media-src vscode-resource: https: data:; script-src 'nonce-${nonce}'; style-src 'self' vscode-resource: 'unsafe-inline' https: data:; font-src vscode-resource: https: data:;">`;
}
}
}

View file

@ -94,13 +94,19 @@ const defaultCssRules = `
}`;
/**
* @typedef {{ postMessage: (channel: string, data?: any) => void, onMessage: (channel: string, handler: any) => void }} HostCommunications
* @typedef {{
* postMessage: (channel: string, data?: any) => void,
* onMessage: (channel: string, handler: any) => void,
* injectHtml?: (document: HTMLDocument) => void,
* preProcessHtml?: (text: string) => void,
* focusIframeOnCreate?: boolean
* }} HostCommunications
*/
/**
* @param {HostCommunications} host
*/
module.exports = function createWebviewManager(host) {
function createWebviewManager(host) {
// state
let firstLoad = true;
let loadTimeout;
@ -212,9 +218,9 @@ module.exports = function createWebviewManager(host) {
return;
}
host.onMessage('styles', (_event, variables, activeTheme) => {
initData.styles = variables;
initData.activeTheme = activeTheme;
host.onMessage('styles', (_event, data) => {
initData.styles = data.styles;
initData.activeTheme = data.activeTheme;
const target = getActiveFrame();
if (!target) {
@ -238,7 +244,7 @@ module.exports = function createWebviewManager(host) {
host.onMessage('content', (_event, data) => {
const options = data.options;
const text = data.contents;
const text = host.preProcessHtml ? host.preProcessHtml(data.contents) : data.contents;
const newDocument = new DOMParser().parseFromString(text, 'text/html');
newDocument.querySelectorAll('a').forEach(a => {
@ -293,6 +299,10 @@ module.exports = function createWebviewManager(host) {
applyStyles(newDocument, newDocument.body);
if (host.injectHtml) {
host.injectHtml(newDocument);
}
const frame = getActiveFrame();
const wasFirstLoad = firstLoad;
// keep current scrollY around and use later
@ -372,7 +382,9 @@ module.exports = function createWebviewManager(host) {
applyStyles(newFrame.contentDocument, newFrame.contentDocument.body);
newFrame.setAttribute('id', 'active-frame');
newFrame.style.visibility = 'visible';
newFrame.contentWindow.focus();
if (host.focusIframeOnCreate) {
newFrame.contentWindow.focus();
}
contentWindow.addEventListener('scroll', handleInnerScroll);
@ -441,6 +453,10 @@ module.exports = function createWebviewManager(host) {
window.onmessage = onMessage;
// signal ready
host.postMessage('webview-ready', process.pid);
host.postMessage('webview-ready', {});
});
};
}
if (typeof module !== 'undefined') {
module.exports = createWebviewManager;
}

View file

@ -0,0 +1,26 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { getMediaMime, MIME_UNKNOWN } from 'vs/base/common/mime';
import { extname } from 'vs/base/common/path';
import { URI } from 'vs/base/common/uri';
const webviewMimeTypes = {
'.svg': 'image/svg+xml',
'.txt': 'text/plain',
'.css': 'text/css',
'.js': 'application/javascript',
'.json': 'application/json',
'.html': 'text/html',
'.htm': 'text/html',
'.xhtml': 'application/xhtml+xml',
'.oft': 'font/otf',
'.xml': 'application/xml',
};
export function getWebviewContentMimeType(normalizedPath: URI): string {
const ext = extname(normalizedPath.fsPath).toLowerCase();
return webviewMimeTypes[ext] || getMediaMime(normalizedPath.fsPath) || MIME_UNKNOWN;
}

View file

@ -0,0 +1,63 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { EDITOR_FONT_DEFAULTS, IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import * as colorRegistry from 'vs/platform/theme/common/colorRegistry';
import { ITheme, LIGHT, DARK } from 'vs/platform/theme/common/themeService';
interface WebviewThemeData {
readonly activeTheme: string;
readonly styles: { readonly [key: string]: string | number };
}
export function getWebviewThemeData(
theme: ITheme,
configurationService: IConfigurationService
): WebviewThemeData {
const configuration = configurationService.getValue<IEditorOptions>('editor');
const editorFontFamily = configuration.fontFamily || EDITOR_FONT_DEFAULTS.fontFamily;
const editorFontWeight = configuration.fontWeight || EDITOR_FONT_DEFAULTS.fontWeight;
const editorFontSize = configuration.fontSize || EDITOR_FONT_DEFAULTS.fontSize;
const exportedColors = colorRegistry.getColorRegistry().getColors().reduce((colors, entry) => {
const color = theme.getColor(entry.id);
if (color) {
colors['vscode-' + entry.id.replace('.', '-')] = color.toString();
}
return colors;
}, {} as { [key: string]: string });
const styles = {
'vscode-font-family': '-apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", ans-serif',
'vscode-font-weight': 'normal',
'vscode-font-size': '13px',
'vscode-editor-font-family': editorFontFamily,
'vscode-editor-font-weight': editorFontWeight,
'vscode-editor-font-size': editorFontSize,
...exportedColors
};
const activeTheme = ApiThemeClassName.fromTheme(theme);
return { styles, activeTheme };
}
enum ApiThemeClassName {
light = 'vscode-light',
dark = 'vscode-dark',
highContrast = 'vscode-high-contrast'
}
namespace ApiThemeClassName {
export function fromTheme(theme: ITheme): ApiThemeClassName {
if (theme.type === LIGHT) {
return ApiThemeClassName.light;
} else if (theme.type === DARK) {
return ApiThemeClassName.dark;
} else {
return ApiThemeClassName.highContrast;
}
}
}

View file

@ -4,12 +4,12 @@
*--------------------------------------------------------------------------------------------*/
import { Event } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import * as modes from 'vs/editor/common/modes';
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import * as modes from 'vs/editor/common/modes';
import { IDisposable } from 'vs/base/common/lifecycle';
/**
* Set when the find widget in a webview is visible.

View file

@ -34,7 +34,8 @@
},
onMessage: (channel, handler) => {
ipcRenderer.on(channel, handler);
}
},
focusIframeOnCreate: true
});
document.addEventListener('DOMContentLoaded', () => {

View file

@ -11,7 +11,6 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { isMacintosh } from 'vs/base/common/platform';
import { endsWith } from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
import { EDITOR_FONT_DEFAULTS, IEditorOptions } from 'vs/editor/common/config/editorOptions';
import * as modes from 'vs/editor/common/modes';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
@ -21,12 +20,12 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import * as colorRegistry from 'vs/platform/theme/common/colorRegistry';
import { DARK, ITheme, IThemeService, LIGHT } from 'vs/platform/theme/common/themeService';
import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService';
import { Webview, WebviewContentOptions, WebviewOptions } from 'vs/workbench/contrib/webview/common/webview';
import { registerFileProtocol, WebviewProtocol } from 'vs/workbench/contrib/webview/electron-browser/webviewProtocols';
import { areWebviewInputOptionsEqual } from '../browser/webviewEditorService';
import { WebviewFindWidget } from '../browser/webviewFindWidget';
import { getWebviewThemeData } from 'vs/workbench/contrib/webview/common/themeing';
export interface WebviewPortMapping {
readonly port: number;
@ -554,11 +553,11 @@ export class WebviewElement extends Disposable implements Webview {
private readonly _onMessage = this._register(new Emitter<any>());
public readonly onMessage = this._onMessage.event;
private _send(channel: string, ...args: any[]): void {
private _send(channel: string, data?: any): void {
this._ready
.then(() => {
if (this._webview) {
this._webview.send(channel, ...args);
this._webview.send(channel, data);
}
})
.catch(err => console.error(err));
@ -647,31 +646,8 @@ export class WebviewElement extends Disposable implements Webview {
}
private style(theme: ITheme): void {
const configuration = this._configurationService.getValue<IEditorOptions>('editor');
const editorFontFamily = configuration.fontFamily || EDITOR_FONT_DEFAULTS.fontFamily;
const editorFontWeight = configuration.fontWeight || EDITOR_FONT_DEFAULTS.fontWeight;
const editorFontSize = configuration.fontSize || EDITOR_FONT_DEFAULTS.fontSize;
const exportedColors = colorRegistry.getColorRegistry().getColors().reduce((colors, entry) => {
const color = theme.getColor(entry.id);
if (color) {
colors['vscode-' + entry.id.replace('.', '-')] = color.toString();
}
return colors;
}, {} as { [key: string]: string });
const styles = {
'vscode-font-family': '-apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif',
'vscode-font-weight': 'normal',
'vscode-font-size': '13px',
'vscode-editor-font-family': editorFontFamily,
'vscode-editor-font-weight': editorFontWeight,
'vscode-editor-font-size': editorFontSize,
...exportedColors
};
const activeTheme = ApiThemeClassName.fromTheme(theme);
this._send('styles', styles, activeTheme);
const { styles, activeTheme } = getWebviewThemeData(theme, this._configurationService);
this._send('styles', { styles, activeTheme });
if (this._webviewFindWidget) {
this._webviewFindWidget.updateTheme(theme);
@ -805,22 +781,3 @@ export class WebviewElement extends Disposable implements Webview {
}
}
}
enum ApiThemeClassName {
light = 'vscode-light',
dark = 'vscode-dark',
highContrast = 'vscode-high-contrast'
}
namespace ApiThemeClassName {
export function fromTheme(theme: ITheme): ApiThemeClassName {
if (theme.type === LIGHT) {
return ApiThemeClassName.light;
} else if (theme.type === DARK) {
return ApiThemeClassName.dark;
} else {
return ApiThemeClassName.highContrast;
}
}
}

View file

@ -2,13 +2,13 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { getMediaMime, MIME_UNKNOWN } from 'vs/base/common/mime';
import { extname, sep } from 'vs/base/common/path';
import * as electron from 'electron';
import { sep } from 'vs/base/common/path';
import { startsWith } from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { IFileService } from 'vs/platform/files/common/files';
import * as electron from 'electron';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { getWebviewContentMimeType } from 'vs/workbench/contrib/webview/common/mimeTypes';
type BufferProtocolCallback = (buffer?: Buffer | electron.MimeTypedBuffer | { error: number }) => void;
@ -54,10 +54,10 @@ export function registerFileProtocol(
requestResourcePath: requestUri.path
})
});
resolveContent(fileService, redirectedUri, getMimeType(requestUri), callback);
resolveContent(fileService, redirectedUri, getWebviewContentMimeType(requestUri), callback);
return;
} else {
resolveContent(fileService, normalizedPath, getMimeType(normalizedPath), callback);
resolveContent(fileService, normalizedPath, getWebviewContentMimeType(normalizedPath), callback);
return;
}
}
@ -70,20 +70,3 @@ export function registerFileProtocol(
});
}
const webviewMimeTypes = {
'.svg': 'image/svg+xml',
'.txt': 'text/plain',
'.css': 'text/css',
'.js': 'application/javascript',
'.json': 'application/json',
'.html': 'text/html',
'.htm': 'text/html',
'.xhtml': 'application/xhtml+xml',
'.oft': 'font/otf',
'.xml': 'application/xml',
};
function getMimeType(normalizedPath: URI): string {
const ext = extname(normalizedPath.fsPath).toLowerCase();
return webviewMimeTypes[ext] || getMediaMime(normalizedPath.fsPath) || MIME_UNKNOWN;
}