SQL Operations Studio Public Preview 1 (0.23) release source code

This commit is contained in:
Karl Burtram
2017-11-09 14:30:27 -08:00
parent b88ecb8d93
commit 3cdac41339
8829 changed files with 759707 additions and 286 deletions

View File

@@ -0,0 +1,132 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./media/htmlPreviewPart';
import URI from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { Position as EditorPosition } from 'vs/platform/editor/common/editor';
import { HtmlInput, HtmlInputOptions } from '../common/htmlInput';
import { HtmlPreviewPart } from 'vs/workbench/parts/html/browser/htmlPreviewPart';
import { Registry } from 'vs/platform/registry/common/platform';
import { EditorDescriptor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/common/editor';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { MenuRegistry } from 'vs/platform/actions/common/actions';
import { WebviewElement } from 'vs/workbench/parts/html/browser/webview';
import { IExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/common/extensions';
function getActivePreviewsForResource(accessor: ServicesAccessor, resource: URI | string) {
const uri = resource instanceof URI ? resource : URI.parse(resource);
return accessor.get(IWorkbenchEditorService).getVisibleEditors()
.filter(c => c instanceof HtmlPreviewPart && c.model)
.map(e => e as HtmlPreviewPart)
.filter(e => e.model.uri.scheme === uri.scheme && e.model.uri.fsPath === uri.fsPath);
}
// --- Register Editor
(<IEditorRegistry>Registry.as(EditorExtensions.Editors)).registerEditor(new EditorDescriptor(HtmlPreviewPart.ID,
localize('html.editor.label', "Html Preview"),
'vs/workbench/parts/html/browser/htmlPreviewPart',
'HtmlPreviewPart'),
[new SyncDescriptor(HtmlInput)]);
// --- Register Commands
const defaultPreviewHtmlOptions: HtmlInputOptions = {
allowScripts: true,
allowSvgs: true
};
CommandsRegistry.registerCommand('_workbench.previewHtml', function (
accessor: ServicesAccessor,
resource: URI | string,
position?: EditorPosition,
label?: string,
options?: HtmlInputOptions
) {
const uri = resource instanceof URI ? resource : URI.parse(resource);
label = label || uri.fsPath;
let input: HtmlInput;
// Find already opened HTML input if any
const stacks = accessor.get(IEditorGroupService).getStacksModel();
const targetGroup = stacks.groupAt(position) || stacks.activeGroup;
if (targetGroup) {
const existingInput = targetGroup.getEditor(uri);
if (existingInput instanceof HtmlInput) {
input = existingInput;
}
}
const inputOptions = (Object as any).assign({}, options || defaultPreviewHtmlOptions);
const extensionsWorkbenchService = accessor.get(IExtensionsWorkbenchService);
inputOptions.svgWhiteList = extensionsWorkbenchService.allowedBadgeProviders;
// Otherwise, create new input and open it
if (!input) {
input = accessor.get(IInstantiationService).createInstance(HtmlInput, label, '', uri, inputOptions);
} else {
input.setName(label); // make sure to use passed in label
}
return accessor.get(IWorkbenchEditorService)
.openEditor(input, { pinned: true }, position)
.then(editor => true);
});
CommandsRegistry.registerCommand('_workbench.htmlPreview.postMessage', function (
accessor: ServicesAccessor,
resource: URI | string,
message: any
) {
const activePreviews = getActivePreviewsForResource(accessor, resource);
for (const preview of activePreviews) {
preview.sendMessage(message);
}
return activePreviews.length > 0;
});
CommandsRegistry.registerCommand('_workbench.htmlPreview.updateOptions', function (
accessor: ServicesAccessor,
resource: URI | string,
options: HtmlInputOptions
) {
const extensionsWorkbenchService = accessor.get(IExtensionsWorkbenchService);
const inputOptions: HtmlInputOptions = options;
const allowedBadgeProviders = extensionsWorkbenchService.allowedBadgeProviders;
inputOptions.svgWhiteList = allowedBadgeProviders;
const uri = resource instanceof URI ? resource : URI.parse(resource);
const activePreviews = getActivePreviewsForResource(accessor, resource);
for (const preview of activePreviews) {
if (preview.input && preview.input instanceof HtmlInput) {
const input = accessor.get(IInstantiationService).createInstance(HtmlInput, preview.input.getName(), '', uri, options);
preview.setInput(input);
}
}
});
CommandsRegistry.registerCommand('_webview.openDevTools', function () {
const elements = document.querySelectorAll('webview.ready');
for (let i = 0; i < elements.length; i++) {
try {
(elements.item(i) as WebviewElement).openDevTools();
} catch (e) {
console.error(e);
}
}
});
MenuRegistry.addCommand({
id: '_webview.openDevTools',
title: localize('devtools.webview', "Developer: Webview Tools")
});

View File

@@ -0,0 +1,236 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { localize } from 'vs/nls';
import { TPromise } from 'vs/base/common/winjs.base';
import { IModel } from 'vs/editor/common/editorCommon';
import { Dimension, Builder } from 'vs/base/browser/builder';
import { empty as EmptyDisposable, IDisposable, dispose, IReference } from 'vs/base/common/lifecycle';
import { EditorOptions, EditorInput } from 'vs/workbench/common/editor';
import { Position } from 'vs/platform/editor/common/editor';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel';
import { HtmlInput, HtmlInputOptions, areHtmlInputOptionsEqual } from 'vs/workbench/parts/html/common/htmlInput';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { ITextModelService, ITextEditorModel } from 'vs/editor/common/services/resolverService';
import { Parts, IPartService } from 'vs/workbench/services/part/common/partService';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import Webview, { WebviewOptions } from './webview';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { WebviewEditor } from './webviewEditor';
/**
* An implementation of editor for showing HTML content in an IFrame by leveraging the HTML input.
*/
export class HtmlPreviewPart extends WebviewEditor {
static ID: string = 'workbench.editor.htmlPreviewPart';
static class: string = 'htmlPreviewPart';
private _webviewDisposables: IDisposable[];
private _modelRef: IReference<ITextEditorModel>;
public get model(): IModel { return this._modelRef && this._modelRef.object.textEditorModel; }
private _modelChangeSubscription = EmptyDisposable;
private _themeChangeSubscription = EmptyDisposable;
private scrollYPercentage: number = 0;
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@ITextModelService private textModelResolverService: ITextModelService,
@IThemeService themeService: IThemeService,
@IOpenerService private readonly openerService: IOpenerService,
@IPartService private partService: IPartService,
@IStorageService storageService: IStorageService,
@IContextViewService private _contextViewService: IContextViewService,
@IContextKeyService contextKeyService: IContextKeyService
) {
super(HtmlPreviewPart.ID, telemetryService, themeService, storageService, contextKeyService);
}
dispose(): void {
// remove from dom
this._webviewDisposables = dispose(this._webviewDisposables);
// unhook listeners
this._themeChangeSubscription.dispose();
this._modelChangeSubscription.dispose();
// dipose model ref
dispose(this._modelRef);
super.dispose();
}
protected createEditor(parent: Builder): void {
this.content = document.createElement('div');
this.content.style.position = 'absolute';
this.content.classList.add(HtmlPreviewPart.class);
parent.getHTMLElement().appendChild(this.content);
}
private get webview(): Webview {
if (!this._webview) {
let webviewOptions: WebviewOptions = {};
if (this.input && this.input instanceof HtmlInput) {
webviewOptions = this.input.options;
}
this._webview = new Webview(this.content, this.partService.getContainer(Parts.EDITOR_PART), this._contextViewService, this.contextKey, this.findInputFocusContextKey, webviewOptions);
if (this.input && this.input instanceof HtmlInput) {
const state = this.loadViewState(this.input.getResource());
this.scrollYPercentage = state ? state.scrollYPercentage : 0;
this.webview.initialScrollProgress = this.scrollYPercentage;
const resourceUri = this.input.getResource();
this.webview.baseUrl = resourceUri.toString(true);
}
this.onThemeChange(this.themeService.getTheme());
this._webviewDisposables = [
this._webview,
this._webview.onDidClickLink(uri => this.openerService.open(uri)),
this._webview.onDidScroll(data => {
this.scrollYPercentage = data.scrollYPercentage;
}),
];
}
return this._webview;
}
public changePosition(position: Position): void {
// what this actually means is that we got reparented. that
// has caused the webview to stop working and we need to reset it
this._doSetVisible(false);
this._doSetVisible(true);
super.changePosition(position);
}
protected setEditorVisible(visible: boolean, position?: Position): void {
this._doSetVisible(visible);
super.setEditorVisible(visible, position);
}
private _doSetVisible(visible: boolean): void {
if (!visible) {
this._themeChangeSubscription.dispose();
this._modelChangeSubscription.dispose();
this._webviewDisposables = dispose(this._webviewDisposables);
this._webview = undefined;
} else {
this._themeChangeSubscription = this.themeService.onThemeChange(this.onThemeChange.bind(this));
if (this._hasValidModel()) {
this._modelChangeSubscription = this.model.onDidChangeContent(() => this.webview.contents = this.model.getLinesContent());
this.webview.contents = this.model.getLinesContent();
}
}
}
private _hasValidModel(): boolean {
return this._modelRef && this.model && !this.model.isDisposed();
}
public layout(dimension: Dimension): void {
const { width, height } = dimension;
this.content.style.width = `${width}px`;
this.content.style.height = `${height}px`;
if (this._webview) {
this._webview.layout();
}
}
public focus(): void {
this.webview.focus();
}
public clearInput(): void {
if (this.input instanceof HtmlInput) {
this.saveViewState(this.input.getResource(), {
scrollYPercentage: this.scrollYPercentage
});
}
dispose(this._modelRef);
this._modelRef = undefined;
super.clearInput();
}
public shutdown(): void {
if (this.input instanceof HtmlInput) {
this.saveViewState(this.input.getResource(), {
scrollYPercentage: this.scrollYPercentage
});
}
super.shutdown();
}
public sendMessage(data: any): void {
this.webview.sendMessage(data);
}
public setInput(input: EditorInput, options?: EditorOptions): TPromise<void> {
if (this.input && this.input.matches(input) && this._hasValidModel() && this.input instanceof HtmlInput && input instanceof HtmlInput && areHtmlInputOptionsEqual(this.input.options, input.options)) {
return TPromise.as(undefined);
}
let oldOptions: HtmlInputOptions | undefined = undefined;
if (this.input instanceof HtmlInput) {
oldOptions = this.input.options;
this.saveViewState(this.input.getResource(), {
scrollYPercentage: this.scrollYPercentage
});
}
if (this._modelRef) {
this._modelRef.dispose();
}
this._modelChangeSubscription.dispose();
if (!(input instanceof HtmlInput)) {
return TPromise.wrapError<void>(new Error('Invalid input'));
}
return super.setInput(input, options).then(() => {
const resourceUri = input.getResource();
return this.textModelResolverService.createModelReference(resourceUri).then(ref => {
const model = ref.object;
if (model instanceof BaseTextEditorModel) {
this._modelRef = ref;
}
if (!this.model) {
return TPromise.wrapError<void>(new Error(localize('html.voidInput', "Invalid editor input.")));
}
if (oldOptions && !areHtmlInputOptionsEqual(oldOptions, input.options)) {
this._doSetVisible(false);
}
this._modelChangeSubscription = this.model.onDidChangeContent(() => {
if (this.model) {
this.scrollYPercentage = 0;
this.webview.contents = this.model.getLinesContent();
}
});
const state = this.loadViewState(resourceUri);
this.scrollYPercentage = state ? state.scrollYPercentage : 0;
this.webview.baseUrl = resourceUri.toString(true);
this.webview.options = input.options;
this.webview.contents = this.model.getLinesContent();
this.webview.initialScrollProgress = this.scrollYPercentage;
return undefined;
});
});
}
}

View File

@@ -0,0 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.htmlPreviewPart {
overflow: hidden;
}

View File

@@ -0,0 +1,271 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// @ts-check
(function () {
'use strict';
const ipcRenderer = require('electron').ipcRenderer;
// state
var firstLoad = true;
var loadTimeout;
var pendingMessages = [];
const initData = {
initialScrollProgress: undefined
};
function styleBody(body) {
if (!body) {
return;
}
body.classList.remove('vscode-light', 'vscode-dark', 'vscode-high-contrast');
body.classList.add(initData.activeTheme);
}
/**
* @return {HTMLIFrameElement}
*/
function getActiveFrame() {
return document.getElementById('active-frame');
}
/**
* @return {HTMLIFrameElement}
*/
function getPendingFrame() {
return document.getElementById('pending-frame');
}
/**
* @param {MouseEvent} event
*/
function handleInnerClick(event) {
if (!event || !event.view || !event.view.document) {
return;
}
/** @type {any} */
var node = event.target;
while (node) {
if (node.tagName && node.tagName.toLowerCase() === 'a' && node.href) {
var baseElement = event.view.document.getElementsByTagName("base")[0];
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))) {
var scrollTarget = event.view.document.getElementById(node.hash.substr(1, node.hash.length - 1));
if (scrollTarget) {
scrollTarget.scrollIntoView();
}
} else {
ipcRenderer.sendToHost("did-click-link", node.href);
}
event.preventDefault();
break;
}
node = node.parentNode;
}
}
var isHandlingScroll = false;
function handleInnerScroll(event) {
if (isHandlingScroll) {
return;
}
const progress = event.target.body.scrollTop / event.target.body.clientHeight;
if (isNaN(progress)) {
return;
}
isHandlingScroll = true;
window.requestAnimationFrame(function () {
try {
ipcRenderer.sendToHost('did-scroll', progress);
} catch (e) {
// noop
}
isHandlingScroll = false;
});
}
document.addEventListener('DOMContentLoaded', function () {
ipcRenderer.on('baseUrl', function (event, value) {
initData.baseUrl = value;
});
ipcRenderer.on('styles', function (event, value, activeTheme) {
initData.styles = value;
initData.activeTheme = activeTheme;
// webview
var target = getActiveFrame();
if (!target) {
return;
}
var body = target.contentDocument.getElementsByTagName('body');
styleBody(body[0]);
// iframe
var defaultStyles = target.contentDocument.getElementById('_defaultStyles');
if (defaultStyles) {
defaultStyles.innerHTML = initData.styles;
}
});
// propagate focus
ipcRenderer.on('focus', function () {
const target = getActiveFrame();
if (target) {
target.contentWindow.focus();
}
});
// update iframe-contents
ipcRenderer.on('content', function (_event, data) {
const options = data.options;
const text = data.contents.join('\n');
const newDocument = new DOMParser().parseFromString(text, 'text/html');
// set base-url if applicable
if (initData.baseUrl && newDocument.head.getElementsByTagName('base').length === 0) {
const baseElement = newDocument.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);
}
styleBody(newDocument.body);
const frame = getActiveFrame();
// keep current scrollTop around and use later
var setInitialScrollPosition;
if (firstLoad) {
firstLoad = false;
setInitialScrollPosition = function (body) {
if (!isNaN(initData.initialScrollProgress)) {
if (body.scrollTop === 0) {
body.scrollTop = body.clientHeight * initData.initialScrollProgress;
}
}
};
} else {
const scrollY = frame && frame.contentDocument && frame.contentDocument.body ? frame.contentDocument.body.scrollTop : 0;
setInitialScrollPosition = function (body) {
if (body.scrollTop === 0) {
body.scrollTop = scrollY;
}
};
}
// Clean up old pending frames and set current one as new one
const previousPendingFrame = getPendingFrame();
if (previousPendingFrame) {
previousPendingFrame.setAttribute('id', '');
document.body.removeChild(previousPendingFrame);
}
pendingMessages = [];
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');
newFrame.style.cssText = "display: block; margin: 0; overflow: hidden; position: absolute; width: 100%; height: 100%; visibility: hidden";
document.body.appendChild(newFrame);
// write new content onto iframe
newFrame.contentDocument.open('text/html', 'replace');
newFrame.contentWindow.onbeforeunload = function () {
console.log('prevented webview navigation');
return false;
};
var onLoad = function (contentDocument, contentWindow) {
if (contentDocument.body) {
// Workaround for https://github.com/Microsoft/vscode/issues/12865
// check new scrollTop and reset if neccessary
setInitialScrollPosition(contentDocument.body);
// Bubble out link clicks
contentDocument.body.addEventListener('click', handleInnerClick);
}
const newFrame = getPendingFrame();
if (newFrame && newFrame.contentDocument === contentDocument) {
const oldActiveFrame = getActiveFrame();
if (oldActiveFrame) {
document.body.removeChild(oldActiveFrame);
}
newFrame.setAttribute('id', 'active-frame');
newFrame.style.visibility = 'visible';
contentWindow.addEventListener('scroll', handleInnerScroll);
pendingMessages.forEach(function(data) {
contentWindow.postMessage(data, document.location.origin);
});
pendingMessages = [];
}
};
clearTimeout(loadTimeout);
loadTimeout = undefined;
loadTimeout = setTimeout(function () {
clearTimeout(loadTimeout);
loadTimeout = undefined;
onLoad(newFrame.contentDocument, newFrame.contentWindow);
}, 200);
newFrame.contentWindow.addEventListener('load', function (e) {
if (loadTimeout) {
clearTimeout(loadTimeout);
loadTimeout = undefined;
onLoad(e.target, this);
}
});
// 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
newFrame.contentDocument.write('<!DOCTYPE html>');
newFrame.contentDocument.write(newDocument.documentElement.innerHTML);
newFrame.contentDocument.close();
ipcRenderer.sendToHost('did-set-content');
});
// Forward message to the embedded iframe
ipcRenderer.on('message', function (event, data) {
const pending = getPendingFrame();
if (pending) {
pendingMessages.push(data);
} else {
const target = getActiveFrame();
if (target) {
target.contentWindow.postMessage(data, document.location.origin);
}
}
});
ipcRenderer.on('initial-scroll-position', function (event, progress) {
initData.initialScrollProgress = progress;
});
// forward messages from the embedded iframe
window.onmessage = function (message) {
ipcRenderer.sendToHost(message.data.command, message.data.data);
};
// signal ready
ipcRenderer.sendToHost('webview-ready', process.pid);
});
}());

View File

@@ -0,0 +1,8 @@
<!DOCTYPE html>
<html lang="en" style="width: 100%; height: 100%">
<head>
<title>Virtual Document</title>
</head>
<body style="margin: 0; overflow: hidden; width: 100%; height: 100%">
</body>
</html>

View File

@@ -0,0 +1,466 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import URI from 'vs/base/common/uri';
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 { editorBackground, editorForeground } from 'vs/platform/theme/common/colorRegistry';
import { ITheme, LIGHT, DARK } from 'vs/platform/theme/common/themeService';
import { WebviewFindWidget } from './webviewFindWidget';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IContextKey } from 'vs/platform/contextkey/common/contextkey';
export declare interface WebviewElement extends HTMLElement {
src: string;
preload: string;
send(channel: string, ...args: any[]);
openDevTools(): any;
getWebContents(): any;
findInPage(value: string, options?: WebviewElementFindInPageOptions);
stopFindInPage(action: string);
}
export class StopFindInPageActions {
static clearSelection = 'clearSelection';
static keepSelection = 'keepSelection';
static activateSelection = 'activateSelection';
}
export interface WebviewElementFindInPageOptions {
forward?: boolean;
findNext?: boolean;
matchCase?: boolean;
wordStart?: boolean;
medialCapitalAsWordStart?: boolean;
}
export interface FoundInPageResults {
requestId: number;
activeMatchOrdinal: number;
matches: number;
selectionArea: any;
}
type ApiThemeClassName = 'vscode-light' | 'vscode-dark' | 'vscode-high-contrast';
export interface WebviewOptions {
allowScripts?: boolean;
allowSvgs?: boolean;
svgWhiteList?: string[];
}
export default class Webview {
private static index: number = 0;
private _webview: WebviewElement;
private _ready: TPromise<this>;
private _disposables: IDisposable[] = [];
private _onDidClickLink = new Emitter<URI>();
private _onDidScroll = new Emitter<{ scrollYPercentage: number }>();
private _onFoundInPageResults = new Emitter<FoundInPageResults>();
private _webviewFindWidget: WebviewFindWidget;
private _findStarted: boolean = false;
constructor(
private parent: HTMLElement,
private _styleElement: Element,
@IContextViewService private _contextViewService: IContextViewService,
private _contextKey: IContextKey<boolean>,
private _findInputContextKey: IContextKey<boolean>,
private _options: WebviewOptions = {},
) {
this._webview = <any>document.createElement('webview');
this._webview.setAttribute('partition', this._options.allowSvgs ? 'webview' : `webview${Webview.index++}`);
// disable auxclick events (see https://developers.google.com/web/updates/2016/10/auxclick)
this._webview.setAttribute('disableblinkfeatures', 'Auxclick');
this._webview.setAttribute('disableguestresize', '');
this._webview.setAttribute('webpreferences', 'contextIsolation=yes');
this._webview.style.flex = '0 1';
this._webview.style.width = '0';
this._webview.style.height = '0';
this._webview.style.outline = '0';
this._webview.preload = require.toUrl('./webview-pre.js');
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);
}
});
});
if (!this._options.allowSvgs) {
let loaded = false;
const subscription = addDisposableListener(this._webview, 'did-start-loading', () => {
if (loaded) {
return;
}
loaded = true;
const contents = this._webview.getWebContents();
if (!contents) {
return;
}
contents.session.webRequest.onBeforeRequest((details, callback) => {
if (details.url.indexOf('.svg') > 0) {
const uri = URI.parse(details.url);
if (uri && !uri.scheme.match(/file/i) && (uri.path as any).endsWith('.svg') && !this.isAllowedSvg(uri)) {
this.onDidBlockSvg();
return callback({ cancel: true });
}
}
return callback({});
});
contents.session.webRequest.onHeadersReceived((details, callback) => {
const contentType: string[] = (details.responseHeaders['content-type'] || details.responseHeaders['Content-Type']) as any;
if (contentType && Array.isArray(contentType) && contentType.some(x => x.toLowerCase().indexOf('image/svg') >= 0)) {
const uri = URI.parse(details.url);
if (uri && !this.isAllowedSvg(uri)) {
this.onDidBlockSvg();
return callback({ cancel: true });
}
}
return callback({ cancel: false, responseHeaders: details.responseHeaders });
});
});
this._disposables.push(subscription);
}
this._disposables.push(
addDisposableListener(this._webview, 'console-message', function (e: { level: number; message: string; line: number; sourceId: string; }) {
console.log(`[Embedded Page] ${e.message}`);
}),
addDisposableListener(this._webview, 'dom-ready', () => {
this.layout();
}),
addDisposableListener(this._webview, 'crashed', () => {
console.error('embedded page crashed');
}),
addDisposableListener(this._webview, 'ipc-message', (event) => {
if (event.channel === 'did-click-link') {
let [uri] = event.args;
this._onDidClickLink.fire(URI.parse(uri));
return;
}
if (event.channel === 'did-set-content') {
this._webview.style.flex = '';
this._webview.style.width = '100%';
this._webview.style.height = '100%';
this.layout();
return;
}
if (event.channel === 'did-scroll') {
if (event.args && typeof event.args[0] === 'number') {
this._onDidScroll.fire({ scrollYPercentage: event.args[0] });
}
return;
}
}),
addDisposableListener(this._webview, 'focus', () => {
if (this._contextKey) {
this._contextKey.set(true);
}
}),
addDisposableListener(this._webview, 'blur', () => {
if (this._contextKey) {
this._contextKey.reset();
}
}),
addDisposableListener(this._webview, 'found-in-page', (event) => {
this._onFoundInPageResults.fire(event.result);
})
);
this._webviewFindWidget = new WebviewFindWidget(this._contextViewService, this);
this._disposables.push(this._webviewFindWidget);
if (parent) {
parent.appendChild(this._webviewFindWidget.getDomNode());
parent.appendChild(this._webview);
}
}
public notifyFindWidgetFocusChanged(isFocused: boolean) {
this._contextKey.set(isFocused || document.activeElement === this._webview);
}
public notifyFindWidgetInputFocusChanged(isFocused: boolean) {
this._findInputContextKey.set(isFocused);
}
dispose(): void {
this._onDidClickLink.dispose();
this._disposables = dispose(this._disposables);
if (this._webview.parentElement) {
this._webview.parentElement.removeChild(this._webview);
const findWidgetDomNode = this._webviewFindWidget.getDomNode();
findWidgetDomNode.parentElement.removeChild(findWidgetDomNode);
}
}
get onDidClickLink(): Event<URI> {
return this._onDidClickLink.event;
}
get onDidScroll(): Event<{ scrollYPercentage: number }> {
return this._onDidScroll.event;
}
get onFindResults(): Event<FoundInPageResults> {
return this._onFoundInPageResults.event;
}
private _send(channel: string, ...args: any[]): void {
this._ready
.then(() => this._webview.send(channel, ...args))
.done(void 0, console.error);
}
set initialScrollProgress(value: number) {
this._send('initial-scroll-position', value);
}
set options(value: WebviewOptions) {
this._options = value;
}
set contents(value: string[]) {
this._send('content', {
contents: value,
options: this._options
});
}
set baseUrl(value: string) {
this._send('baseUrl', value);
}
focus(): void {
this._webview.focus();
this._send('focus');
}
public sendMessage(data: any): void {
this._send('message', data);
}
private onDidBlockSvg() {
this.sendMessage({
name: 'vscode-did-block-svg'
});
}
style(theme: ITheme): void {
const { fontFamily, fontWeight, fontSize } = window.getComputedStyle(this._styleElement); // TODO@theme avoid styleElement
let value = `
:root {
--background-color: ${theme.getColor(editorBackground)};
--color: ${theme.getColor(editorForeground)};
--font-family: ${fontFamily};
--font-weight: ${fontWeight};
--font-size: ${fontSize};
}
body {
background-color: var(--background-color);
color: var(--color);
font-family: var(--font-family);
font-weight: var(--font-weight);
font-size: var(--font-size);
margin: 0;
padding: 0 20px;
}
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 {
width: 10px;
height: 10px;
}`;
let activeTheme: ApiThemeClassName;
if (theme.type === LIGHT) {
value += `
::-webkit-scrollbar-thumb {
background-color: rgba(100, 100, 100, 0.4);
}
::-webkit-scrollbar-thumb:hover {
background-color: rgba(100, 100, 100, 0.7);
}
::-webkit-scrollbar-thumb:active {
background-color: rgba(0, 0, 0, 0.6);
}`;
activeTheme = 'vscode-light';
} else if (theme.type === DARK) {
value += `
::-webkit-scrollbar-thumb {
background-color: rgba(121, 121, 121, 0.4);
}
::-webkit-scrollbar-thumb:hover {
background-color: rgba(100, 100, 100, 0.7);
}
::-webkit-scrollbar-thumb:active {
background-color: rgba(85, 85, 85, 0.8);
}`;
activeTheme = 'vscode-dark';
} else {
value += `
::-webkit-scrollbar-thumb {
background-color: rgba(111, 195, 223, 0.3);
}
::-webkit-scrollbar-thumb:hover {
background-color: rgba(111, 195, 223, 0.8);
}
::-webkit-scrollbar-thumb:active {
background-color: rgba(111, 195, 223, 0.8);
}`;
activeTheme = 'vscode-high-contrast';
}
this._send('styles', value, activeTheme);
this._webviewFindWidget.updateTheme(theme);
}
public layout(): void {
const contents = (this._webview as any).getWebContents();
if (!contents || contents.isDestroyed()) {
return;
}
const window = contents.getOwnerBrowserWindow();
if (!window || !window.webContents || window.webContents.isDestroyed()) {
return;
}
window.webContents.getZoomFactor(factor => {
if (contents.isDestroyed()) {
return;
}
contents.setZoomFactor(factor);
const width = this.parent.clientWidth;
const height = this.parent.clientHeight;
contents.setSize({
normal: {
width: Math.floor(width * factor),
height: Math.floor(height * factor)
}
});
});
}
private isAllowedSvg(uri: URI): boolean {
if (this._options.allowSvgs) {
return true;
}
if (this._options.svgWhiteList) {
return this._options.svgWhiteList.indexOf(uri.authority.toLowerCase()) >= 0;
}
return false;
}
public startFind(value: string, options?: WebviewElementFindInPageOptions) {
if (!value) {
return;
}
// ensure options is defined without modifying the original
options = options || {};
// FindNext must be false for a first request
const findOptions: WebviewElementFindInPageOptions = {
forward: options.forward,
findNext: false,
matchCase: options.matchCase,
medialCapitalAsWordStart: options.medialCapitalAsWordStart
};
this._findStarted = true;
this._webview.findInPage(value, findOptions);
return;
}
/**
* Webviews expose a stateful find API.
* Successive calls to find will move forward or backward through onFindResults
* depending on the supplied options.
*
* @param {string} value The string to search for. Empty strings are ignored.
* @param {WebviewElementFindInPageOptions} [options]
*
* @memberOf Webview
*/
public find(value: string, options?: WebviewElementFindInPageOptions): void {
// Searching with an empty value will throw an exception
if (!value) {
return;
}
if (!this._findStarted) {
this.startFind(value, options);
return;
}
this._webview.findInPage(value, options);
}
public stopFind(keepSelection?: boolean): void {
this._findStarted = false;
this._webview.stopFindInPage(keepSelection ? StopFindInPageActions.keepSelection : StopFindInPageActions.clearSelection);
}
public showFind() {
this._webviewFindWidget.reveal();
}
public hideFind() {
this._webviewFindWidget.hide();
}
public showNextFindTerm() {
this._webviewFindWidget.showNextFindTerm();
}
public showPreviousFindTerm() {
this._webviewFindWidget.showPreviousFindTerm();
}
}

View File

@@ -0,0 +1,188 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { BaseWebviewEditor } from 'vs/workbench/browser/parts/editor/webviewEditor';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { Command, ICommandOptions } from 'vs/editor/common/editorCommonExtensions';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { ContextKeyExpr, IContextKey, RawContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
import WebView from './webview';
import { Builder } from 'vs/base/browser/builder';
export interface HtmlPreviewEditorViewState {
scrollYPercentage: number;
}
/** A context key that is set when a webview editor has focus. */
export const KEYBINDING_CONTEXT_WEBVIEWEDITOR_FOCUS = new RawContextKey<boolean>('webviewEditorFocus', undefined);
/** A context key that is set when a webview editor does not have focus. */
export const KEYBINDING_CONTEXT_WEBVIEWEDITOR_NOT_FOCUSED: ContextKeyExpr = KEYBINDING_CONTEXT_WEBVIEWEDITOR_FOCUS.toNegated();
/** A context key that is set when the find widget find input in webview editor webview is focused. */
export const KEYBINDING_CONTEXT_WEBVIEWEDITOR_FIND_WIDGET_INPUT_FOCUSED = new RawContextKey<boolean>('webviewEditorFindWidgetInputFocused', false);
/** A context key that is set when the find widget find input in webview editor webview is not focused. */
export const KEYBINDING_CONTEXT_WEBVIEWEDITOR_FIND_WIDGET_INPUT_NOT_FOCUSED: ContextKeyExpr = KEYBINDING_CONTEXT_WEBVIEWEDITOR_FIND_WIDGET_INPUT_FOCUSED.toNegated();
/**
* This class is only intended to be subclassed and not instantiated.
*/
export abstract class WebviewEditor extends BaseWebviewEditor {
protected _webviewFocusContextKey: IContextKey<boolean>;
protected _webview: WebView;
protected content: HTMLElement;
protected contextKey: IContextKey<boolean>;
protected findInputFocusContextKey: IContextKey<boolean>;
constructor(
id: string,
telemetryService: ITelemetryService,
themeService: IThemeService,
storageService: IStorageService,
contextKeyService: IContextKeyService,
) {
super(id, telemetryService, themeService, storageService);
if (contextKeyService) {
this.contextKey = KEYBINDING_CONTEXT_WEBVIEWEDITOR_FOCUS.bindTo(contextKeyService);
this.findInputFocusContextKey = KEYBINDING_CONTEXT_WEBVIEWEDITOR_FIND_WIDGET_INPUT_FOCUSED.bindTo(contextKeyService);
}
}
public showFind() {
if (this._webview) {
this._webview.showFind();
}
}
public hideFind() {
if (this._webview) {
this._webview.hideFind();
}
}
public showNextFindTerm() {
if (this._webview) {
this._webview.showNextFindTerm();
}
}
public showPreviousFindTerm() {
if (this._webview) {
this._webview.showPreviousFindTerm();
}
}
public updateStyles() {
super.updateStyles();
if (this._webview) {
this._webview.style(this.themeService.getTheme());
}
}
public get isWebviewEditor() {
return true;
}
protected abstract createEditor(parent: Builder);
}
class ShowWebViewEditorFindCommand extends Command {
public runCommand(accessor: ServicesAccessor, args: any): void {
const webViewEditor = this.getWebViewEditor(accessor);
if (webViewEditor) {
webViewEditor.showFind();
}
}
private getWebViewEditor(accessor: ServicesAccessor): WebviewEditor {
const activeEditor = accessor.get(IWorkbenchEditorService).getActiveEditor() as WebviewEditor;
if (activeEditor.isWebviewEditor) {
return activeEditor;
}
return null;
}
}
const showFindCommand = new ShowWebViewEditorFindCommand({
id: 'editor.action.webvieweditor.showFind',
precondition: KEYBINDING_CONTEXT_WEBVIEWEDITOR_FOCUS,
kbOpts: {
primary: KeyMod.CtrlCmd | KeyCode.KEY_F
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule(showFindCommand.toCommandAndKeybindingRule(KeybindingsRegistry.WEIGHT.editorContrib()));
class HideWebViewEditorFindCommand extends Command {
public runCommand(accessor: ServicesAccessor, args: any): void {
const webViewEditor = this.getWebViewEditor(accessor);
if (webViewEditor) {
webViewEditor.hideFind();
}
}
private getWebViewEditor(accessor: ServicesAccessor): WebviewEditor {
const activeEditor = accessor.get(IWorkbenchEditorService).getActiveEditor() as WebviewEditor;
if (activeEditor.isWebviewEditor) {
return activeEditor;
}
return null;
}
}
const hideCommand = new HideWebViewEditorFindCommand({
id: 'editor.action.webvieweditor.hideFind',
precondition: KEYBINDING_CONTEXT_WEBVIEWEDITOR_FOCUS,
kbOpts: {
primary: KeyCode.Escape
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule(hideCommand.toCommandAndKeybindingRule(KeybindingsRegistry.WEIGHT.editorContrib()));
class ShowWebViewEditorFindTermCommand extends Command {
constructor(opts: ICommandOptions, private _next: boolean) {
super(opts);
}
public runCommand(accessor: ServicesAccessor, args: any): void {
const webViewEditor = this.getWebViewEditor(accessor);
if (webViewEditor) {
if (this._next) {
webViewEditor.showNextFindTerm();
} else {
webViewEditor.showPreviousFindTerm();
}
}
}
private getWebViewEditor(accessor: ServicesAccessor): WebviewEditor {
const activeEditor = accessor.get(IWorkbenchEditorService).getActiveEditor() as WebviewEditor;
if (activeEditor.isWebviewEditor) {
return activeEditor;
}
return null;
}
}
const showNextFindTermCommand = new ShowWebViewEditorFindTermCommand({
id: 'editor.action.webvieweditor.showNextFindTerm',
precondition: KEYBINDING_CONTEXT_WEBVIEWEDITOR_FIND_WIDGET_INPUT_FOCUSED,
kbOpts: {
primary: KeyMod.Alt | KeyCode.DownArrow
}
}, true);
KeybindingsRegistry.registerCommandAndKeybindingRule(showNextFindTermCommand.toCommandAndKeybindingRule(KeybindingsRegistry.WEIGHT.editorContrib()));
const showPreviousFindTermCommand = new ShowWebViewEditorFindTermCommand({
id: 'editor.action.webvieweditor.showPreviousFindTerm',
precondition: KEYBINDING_CONTEXT_WEBVIEWEDITOR_FIND_WIDGET_INPUT_FOCUSED,
kbOpts: {
primary: KeyMod.Alt | KeyCode.UpArrow
}
}, false);
KeybindingsRegistry.registerCommandAndKeybindingRule(showPreviousFindTermCommand.toCommandAndKeybindingRule(KeybindingsRegistry.WEIGHT.editorContrib()));

View File

@@ -0,0 +1,64 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SimpleFindWidget } from 'vs/editor/contrib/find/browser/simpleFindWidget';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import Webview from './webview';
export class WebviewFindWidget extends SimpleFindWidget {
constructor(
@IContextViewService _contextViewService: IContextViewService,
private webview: Webview
) {
super(_contextViewService);
this.find = this.find.bind(this);
this.hide = this.hide.bind(this);
this.onInputChanged = this.onInputChanged.bind(this);
}
public find(previous) {
let val = this.inputValue;
if (this.webview !== null && val) {
this.webview.find(val, { findNext: true, forward: !previous });
}
};
public hide() {
super.hide();
this.webview.stopFind(true);
this.webview.focus();
}
public onInputChanged() {
if (!this.webview) {
return;
}
let val = this.inputValue;
if (val) {
this.webview.startFind(val);
} else {
this.webview.stopFind(false);
}
}
protected onFocusTrackerFocus() {
this.webview.notifyFindWidgetFocusChanged(true);
}
protected onFocusTrackerBlur() {
this.webview.notifyFindWidgetFocusChanged(false);
}
protected onFindInputFocusTrackerFocus() {
this.webview.notifyFindWidgetInputFocusChanged(true);
}
protected onFindInputFocusTrackerBlur() {
this.webview.notifyFindWidgetInputFocusChanged(false);
}
}

View File

@@ -0,0 +1,32 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import URI from 'vs/base/common/uri';
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
export interface HtmlInputOptions {
allowScripts?: boolean;
allowSvgs?: boolean;
svgWhiteList?: string[];
}
export function areHtmlInputOptionsEqual(left: HtmlInputOptions, right: HtmlInputOptions) {
return left.allowScripts === right.allowScripts && left.allowSvgs === right.allowSvgs;
}
export class HtmlInput extends ResourceEditorInput {
constructor(
name: string,
description: string,
resource: URI,
public readonly options: HtmlInputOptions,
@ITextModelService textModelResolverService: ITextModelService
) {
super(name, description, resource, textModelResolverService);
}
}