Merge VS Code 1.23.1 (#1520)

This commit is contained in:
Matt Irvine
2018-06-05 11:24:51 -07:00
committed by GitHub
parent e3baf5c443
commit 0c58f09e59
3651 changed files with 74249 additions and 48599 deletions

View File

@@ -2,501 +2,117 @@
* 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 { IDisposable, dispose } from 'vs/base/common/lifecycle';
import * as map from 'vs/base/common/map';
import { TPromise } from 'vs/base/common/winjs.base';
import { MainThreadWebviewsShape, MainContext, IExtHostContext, ExtHostContext, ExtHostWebviewsShape, WebviewHandle } from 'vs/workbench/api/node/extHost.protocol';
import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle';
import { extHostNamedCustomer } from './extHostCustomers';
import { EditorInput, EditorModel, EditorOptions } from 'vs/workbench/common/editor';
import { IEditorModel, Position } from 'vs/platform/editor/common/editor';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { WebviewEditor as BaseWebviewEditor, KEYBINDING_CONTEXT_WEBVIEWEDITOR_FOCUS, KEYBINDING_CONTEXT_WEBVIEWEDITOR_FIND_WIDGET_INPUT_FOCUSED, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE } from 'vs/workbench/parts/html/browser/webviewEditor';
import { Builder, Dimension } from 'vs/base/browser/builder';
import { Webview } from 'vs/workbench/parts/html/browser/webview';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IPartService, Parts } from 'vs/workbench/services/part/common/partService';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor';
import { Registry } from 'vs/platform/registry/common/platform';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { localize } from 'vs/nls';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import * as vscode from 'vscode';
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import URI from 'vs/base/common/uri';
import DOM = require('vs/base/browser/dom');
import Event, { Emitter } from 'vs/base/common/event';
interface WebviewEvents {
onMessage(message: any): void;
onDidChangePosition(newPosition: Position): void;
onDispose(): void;
onDidClickLink(link: URI, options: vscode.WebviewOptions): void;
}
class WebviewInput extends EditorInput {
private static handlePool = 0;
private readonly _resource: URI;
private _name: string;
private _options: vscode.WebviewOptions;
private _html: string;
private _currentWebviewHtml: string = '';
private _events: WebviewEvents | undefined;
private _container: HTMLElement;
private _webview: Webview | undefined;
private _webviewOwner: any;
private _webviewDisposables: IDisposable[] = [];
private _position: Position;
public static create(
resource: URI,
name: string,
position: Position,
options: vscode.WebviewOptions,
html: string,
events: WebviewEvents,
partService: IPartService
): WebviewInput {
const id = WebviewInput.handlePool++;
const webviewContainer = document.createElement('div');
webviewContainer.id = `webview-${id}`;
partService.getContainer(Parts.EDITOR_PART).appendChild(webviewContainer);
return new WebviewInput(resource, name, position, options, html, events, webviewContainer, undefined);
}
constructor(
resource: URI,
name: string,
position: Position,
options: vscode.WebviewOptions,
html: string,
events: WebviewEvents,
container: HTMLElement,
webview: Webview | undefined
) {
super();
this._resource = resource;
this._name = name;
this._position = position;
this._options = options;
this._html = html;
this._events = events;
this._container = container;
this._webview = webview;
}
public getTypeId(): string {
return 'webview';
}
public dispose() {
this.disposeWebview();
if (this._container) {
this._container.remove();
this._container = undefined;
}
if (this._events) {
this._events.onDispose();
this._events = undefined;
}
super.dispose();
}
public getResource(): URI {
return this._resource;
}
public getName(): string {
return this._name;
}
public setName(value: string): void {
this._name = value;
this._onDidChangeLabel.fire();
}
public get position(): Position {
return this._position;
}
public get html(): string {
return this._html;
}
public setHtml(value: string): void {
if (value === this._currentWebviewHtml) {
return;
}
this._html = value;
if (this._webview) {
this._webview.contents = value;
this._currentWebviewHtml = value;
}
}
public get options(): vscode.WebviewOptions {
return this._options;
}
public set options(value: vscode.WebviewOptions) {
this._options = value;
}
public resolve(refresh?: boolean): TPromise<IEditorModel, any> {
return TPromise.as(new EditorModel());
}
public supportsSplitEditor() {
return false;
}
public get container(): HTMLElement {
return this._container;
}
public get webview(): Webview | undefined {
return this._webview;
}
public set webview(value: Webview) {
this._webviewDisposables = dispose(this._webviewDisposables);
this._webview = value;
this._webview.onDidClickLink(link => {
if (this._events) {
this._events.onDidClickLink(link, this._options);
}
}, null, this._webviewDisposables);
this._webview.onMessage(message => {
if (this._events) {
this._events.onMessage(message);
}
}, null, this._webviewDisposables);
}
public claimWebview(owner: any) {
this._webviewOwner = owner;
}
public releaseWebview(owner: any) {
if (this._webviewOwner === owner) {
this._webviewOwner = undefined;
if (this._options.retainContextWhenHidden) {
this.container.style.visibility = 'hidden';
} else {
this.disposeWebview();
}
}
}
public disposeWebview() {
// The input owns the webview and its parent
if (this._webview) {
this._webview.dispose();
this._webview = undefined;
}
this._webviewDisposables = dispose(this._webviewDisposables);
this._webviewOwner = undefined;
this.container.style.visibility = 'hidden';
this._currentWebviewHtml = '';
}
public onDidChangePosition(position: Position) {
if (this._events) {
this._events.onDidChangePosition(position);
}
this._position = position;
}
}
class WebviewEditor extends BaseWebviewEditor {
public static readonly ID = 'WebviewEditor';
private editorFrame: HTMLElement;
private webviewContent: HTMLElement;
private _onDidFocusWebview: Emitter<void>;
private _webviewFocusTracker?: DOM.IFocusTracker;
private _webviewFocusListenerDisposable?: IDisposable;
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IStorageService storageService: IStorageService,
@IThemeService themeService: IThemeService,
@IContextKeyService private _contextKeyService: IContextKeyService,
@IPartService private readonly _partService: IPartService,
@IContextViewService private readonly _contextViewService: IContextViewService,
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService
) {
super(WebviewEditor.ID, telemetryService, themeService, storageService, _contextKeyService);
this._onDidFocusWebview = new Emitter<void>();
}
protected createEditor(parent: Builder): void {
this.editorFrame = parent.getHTMLElement();
this.content = document.createElement('div');
parent.append(this.content);
}
private doUpdateContainer() {
const webviewContainer = this.input && (this.input as WebviewInput).container;
if (webviewContainer) {
const frameRect = this.editorFrame.getBoundingClientRect();
const containerRect = webviewContainer.parentElement.getBoundingClientRect();
webviewContainer.style.position = 'absolute';
webviewContainer.style.top = `${frameRect.top - containerRect.top}px`;
webviewContainer.style.left = `${frameRect.left - containerRect.left}px`;
webviewContainer.style.width = `${frameRect.width}px`;
webviewContainer.style.height = `${frameRect.height}px`;
}
}
public layout(dimension: Dimension): void {
if (this._webview) {
this.doUpdateContainer();
}
super.layout(dimension);
}
public dispose(): void {
// Let the editor input dispose of the webview.
this._webview = undefined;
this.webviewContent = undefined;
this._onDidFocusWebview.dispose();
if (this._webviewFocusTracker) {
this._webviewFocusTracker.dispose();
}
if (this._webviewFocusListenerDisposable) {
this._webviewFocusListenerDisposable.dispose();
}
super.dispose();
}
public sendMessage(data: any): void {
if (this._webview) {
this._webview.sendMessage(data);
}
}
public get onDidFocus(): Event<any> {
return this._onDidFocusWebview.event;
}
protected setEditorVisible(visible: boolean, position?: Position): void {
if (this.input && this.input instanceof WebviewInput) {
if (visible) {
this.input.claimWebview(this);
} else {
this.input.releaseWebview(this);
}
this.updateWebview(this.input as WebviewInput);
}
if (this.webviewContent) {
if (visible) {
this.webviewContent.style.visibility = 'visible';
this.doUpdateContainer();
} else {
this.webviewContent.style.visibility = 'hidden';
}
}
super.setEditorVisible(visible, position);
}
public clearInput() {
if (this.input && this.input instanceof WebviewInput) {
this.input.releaseWebview(this);
}
this._webview = undefined;
this.webviewContent = undefined;
super.clearInput();
}
async setInput(input: WebviewInput, options: EditorOptions): TPromise<void> {
if (this.input && this.input.matches(input)) {
return undefined;
}
if (this.input && this.input.getResource().fsPath !== input.getResource().fsPath) {
(this.input as WebviewInput).releaseWebview(this);
this._webview = undefined;
this.webviewContent = undefined;
}
await super.setInput(input, options);
input.onDidChangePosition(this.position);
this.updateWebview(input);
}
private updateWebview(input: WebviewInput) {
const webview = this.getWebview(input);
input.claimWebview(this);
webview.options = {
allowScripts: input.options.enableScripts,
enableWrappedPostMessage: true,
useSameOriginForRoot: false,
localResourceRoots: (input && input.options.localResourceRoots) || this._contextService.getWorkspace().folders.map(x => x.uri)
};
input.setHtml(input.html);
this.webviewContent.style.visibility = 'visible';
this.doUpdateContainer();
}
private getWebview(input: WebviewInput): Webview {
if (this._webview) {
return this._webview;
}
this.webviewContent = input.container;
const existing = input.webview;
if (existing) {
this._webview = existing;
return existing;
}
this._webviewFocusTracker = DOM.trackFocus(this.webviewContent);
this._webviewFocusListenerDisposable = this._webviewFocusTracker.onDidFocus(() => {
this._onDidFocusWebview.fire();
});
this._contextKeyService = this._contextKeyService.createScoped(this.webviewContent);
this.contextKey = KEYBINDING_CONTEXT_WEBVIEWEDITOR_FOCUS.bindTo(this._contextKeyService);
this.findInputFocusContextKey = KEYBINDING_CONTEXT_WEBVIEWEDITOR_FIND_WIDGET_INPUT_FOCUSED.bindTo(this._contextKeyService);
this.findWidgetVisible = KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE.bindTo(this._contextKeyService);
this._webview = new Webview(
this.webviewContent,
this._partService.getContainer(Parts.EDITOR_PART),
this.themeService,
this._environmentService,
this._contextViewService,
this.contextKey,
this.findInputFocusContextKey,
{
enableWrappedPostMessage: true,
useSameOriginForRoot: false
});
input.webview = this._webview;
this.content.setAttribute('aria-flowto', this.webviewContent.id);
this.doUpdateContainer();
return this._webview;
}
}
import { TPromise } from 'vs/base/common/winjs.base';
import { localize } from 'vs/nls';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { Position } from 'vs/platform/editor/common/editor';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { ExtHostContext, ExtHostWebviewsShape, IExtHostContext, MainContext, MainThreadWebviewsShape, WebviewPanelHandle } from 'vs/workbench/api/node/extHost.protocol';
import { WebviewEditor } from 'vs/workbench/parts/webview/electron-browser/webviewEditor';
import { WebviewEditorInput } from 'vs/workbench/parts/webview/electron-browser/webviewEditorInput';
import { IWebviewEditorService, WebviewInputOptions, WebviewReviver } from 'vs/workbench/parts/webview/electron-browser/webviewEditorService';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { extHostNamedCustomer } from './extHostCustomers';
@extHostNamedCustomer(MainContext.MainThreadWebviews)
export class MainThreadWebviews implements MainThreadWebviewsShape {
export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviver {
private static readonly serializeTimeout = 500; // ms
private static readonly viewType = 'mainThreadWebview';
private static readonly standardSupportedLinkSchemes = ['http', 'https', 'mailto'];
private _toDispose: Disposable[] = [];
private static revivalPool = 0;
private _toDispose: IDisposable[] = [];
private readonly _proxy: ExtHostWebviewsShape;
private readonly _webviews = new Map<WebviewHandle, WebviewInput>();
private readonly _webviews = new Map<WebviewPanelHandle, WebviewEditorInput>();
private readonly _revivers = new Set<string>();
private _activeWebview: WebviewInput | undefined = undefined;
private _activeWebview: WebviewPanelHandle | undefined = undefined;
constructor(
context: IExtHostContext,
@IContextKeyService _contextKeyService: IContextKeyService,
@IPartService private readonly _partService: IPartService,
@IContextKeyService contextKeyService: IContextKeyService,
@IEditorGroupService editorGroupService: IEditorGroupService,
@ILifecycleService lifecycleService: ILifecycleService,
@IWorkbenchEditorService private readonly _editorService: IWorkbenchEditorService,
@IEditorGroupService private readonly _editorGroupService: IEditorGroupService,
@IOpenerService private readonly _openerService: IOpenerService
@IWebviewEditorService private readonly _webviewService: IWebviewEditorService,
@IOpenerService private readonly _openerService: IOpenerService,
@IExtensionService private readonly _extensionService: IExtensionService,
) {
this._proxy = context.getProxy(ExtHostContext.ExtHostWebviews);
_editorGroupService.onEditorsChanged(this.onEditorsChanged, this, this._toDispose);
editorGroupService.onEditorsChanged(this.onEditorsChanged, this, this._toDispose);
editorGroupService.onEditorGroupMoved(this.onEditorGroupMoved, this, this._toDispose);
this._toDispose.push(_webviewService.registerReviver(MainThreadWebviews.viewType, this));
lifecycleService.onWillShutdown(e => {
e.veto(this._onWillShutdown());
}, this, this._toDispose);
}
dispose(): void {
this._toDispose = dispose(this._toDispose);
}
$createWebview(handle: WebviewHandle, uri: URI, title: string, column: Position, options: vscode.WebviewOptions): void {
const webviewInput = WebviewInput.create(URI.revive(uri), title, column, options, '', {
onMessage: message => this._proxy.$onMessage(handle, message),
onDidChangePosition: position => this._proxy.$onDidChangePosition(handle, position),
onDispose: () => {
this._proxy.$onDidDisposeWeview(handle).then(() => {
this._webviews.delete(handle);
});
},
onDidClickLink: (link, options) => this.onDidClickLink(link, options)
}, this._partService);
$createWebviewPanel(
handle: WebviewPanelHandle,
viewType: string,
title: string,
column: Position,
options: WebviewInputOptions,
extensionFolderPath: string
): void {
const webview = this._webviewService.createWebview(MainThreadWebviews.viewType, title, column, options, extensionFolderPath, this.createWebviewEventDelegate(handle));
webview.state = {
viewType: viewType,
state: undefined
};
this._webviews.set(handle, webviewInput);
this._editorService.openEditor(webviewInput, { pinned: true }, column);
this._webviews.set(handle, webview);
this._activeWebview = handle;
}
$disposeWebview(handle: WebviewHandle): void {
$disposeWebview(handle: WebviewPanelHandle): void {
const webview = this.getWebview(handle);
if (webview) {
this._editorService.closeEditor(webview.position, webview);
}
webview.dispose();
}
$setTitle(handle: WebviewHandle, value: string): void {
$setTitle(handle: WebviewPanelHandle, value: string): void {
const webview = this.getWebview(handle);
webview.setName(value);
}
$setHtml(handle: WebviewHandle, value: string): void {
$setHtml(handle: WebviewPanelHandle, value: string): void {
const webview = this.getWebview(handle);
webview.setHtml(value);
webview.html = value;
}
$show(handle: WebviewHandle, column: Position): void {
const webviewInput = this.getWebview(handle);
if (webviewInput.position === column) {
this._editorService.openEditor(webviewInput, { preserveFocus: true }, column);
} else {
this._editorGroupService.moveEditor(webviewInput, webviewInput.position, column, { preserveFocus: true });
$reveal(handle: WebviewPanelHandle, column: Position | null): void {
const webview = this.getWebview(handle);
if (webview.isDisposed()) {
return;
}
this._webviewService.revealWebview(webview, column);
}
async $sendMessage(handle: WebviewHandle, message: any): Promise<boolean> {
const webviewInput = this.getWebview(handle);
async $postMessage(handle: WebviewPanelHandle, message: any): TPromise<boolean> {
const webview = this.getWebview(handle);
const editors = this._editorService.getVisibleEditors()
.filter(e => e instanceof WebviewEditor)
.map(e => e as WebviewEditor)
.filter(e => e.input.matches(webviewInput));
.filter(e => e.input.matches(webview));
for (const editor of editors) {
editor.sendMessage(message);
@@ -505,18 +121,91 @@ export class MainThreadWebviews implements MainThreadWebviewsShape {
return (editors.length > 0);
}
private getWebview(handle: number): WebviewInput {
const webviewInput = this._webviews.get(handle);
if (!webviewInput) {
$registerSerializer(viewType: string): void {
this._revivers.add(viewType);
}
$unregisterSerializer(viewType: string): void {
this._revivers.delete(viewType);
}
reviveWebview(webview: WebviewEditorInput): TPromise<void> {
const viewType = webview.state.viewType;
return this._extensionService.activateByEvent(`onView:${viewType}`).then(() => {
const handle = 'revival-' + MainThreadWebviews.revivalPool++;
this._webviews.set(handle, webview);
webview._events = this.createWebviewEventDelegate(handle);
return this._proxy.$deserializeWebviewPanel(handle, webview.state.viewType, webview.getTitle(), webview.state.state, webview.position, webview.options)
.then(undefined, () => {
webview.html = MainThreadWebviews.getDeserializationFailedContents(viewType);
});
});
}
canRevive(webview: WebviewEditorInput): boolean {
if (webview.isDisposed()) {
return false;
}
return this._revivers.has(webview.viewType) || webview.reviver !== null;
}
private _onWillShutdown(): TPromise<boolean> {
const toRevive: WebviewPanelHandle[] = [];
this._webviews.forEach((view, key) => {
if (this.canRevive(view)) {
toRevive.push(key);
}
});
const reviveResponses = toRevive.map(handle =>
TPromise.any([
this._proxy.$serializeWebviewPanel(handle).then(
state => ({ handle, state }),
() => ({ handle, state: null })),
TPromise.timeout(MainThreadWebviews.serializeTimeout).then(() => ({ handle, state: null }))
]).then(x => x.value));
return TPromise.join(reviveResponses).then(results => {
for (const result of results) {
const view = this._webviews.get(result.handle);
if (view) {
if (result.state) {
view.state.state = result.state;
} else {
view.state = null;
}
}
}
return false; // Don't veto shutdown
});
}
private createWebviewEventDelegate(handle: WebviewPanelHandle) {
return {
onDidClickLink: uri => this.onDidClickLink(handle, uri),
onMessage: message => this._proxy.$onMessage(handle, message),
onDispose: () => {
this._proxy.$onDidDisposeWebviewPanel(handle).then(
() => this._webviews.delete(handle),
() => this._webviews.delete(handle));
}
};
}
private getWebview(handle: WebviewPanelHandle): WebviewEditorInput {
const webview = this._webviews.get(handle);
if (!webview) {
throw new Error('Unknown webview handle:' + handle);
}
return webviewInput;
return webview;
}
private onEditorsChanged() {
const activeEditor = this._editorService.getActiveEditor();
let newActiveWebview: { input: WebviewInput, handle: WebviewHandle } | undefined = undefined;
if (activeEditor && activeEditor.input instanceof WebviewInput) {
let newActiveWebview: { input: WebviewEditorInput, handle: WebviewPanelHandle } | undefined = undefined;
if (activeEditor && activeEditor.input instanceof WebviewEditorInput) {
for (const handle of map.keys(this._webviews)) {
const input = this._webviews.get(handle);
if (input.matches(activeEditor.input)) {
@@ -526,33 +215,64 @@ export class MainThreadWebviews implements MainThreadWebviewsShape {
}
}
if (newActiveWebview && newActiveWebview.handle === this._activeWebview) {
// Webview itself unchanged but position may have changed
this._proxy.$onDidChangeWebviewPanelViewState(newActiveWebview.handle, true, newActiveWebview.input.position);
return;
}
// Broadcast view state update for currently active
if (typeof this._activeWebview !== 'undefined') {
const oldActiveWebview = this._webviews.get(this._activeWebview);
if (oldActiveWebview) {
this._proxy.$onDidChangeWebviewPanelViewState(this._activeWebview, false, oldActiveWebview.position);
}
}
// Then for newly active
if (newActiveWebview) {
if (!this._activeWebview || !newActiveWebview.input.matches(this._activeWebview)) {
this._proxy.$onDidChangeActiveWeview(newActiveWebview.handle);
this._activeWebview = newActiveWebview.input;
}
this._proxy.$onDidChangeWebviewPanelViewState(newActiveWebview.handle, true, activeEditor.position);
this._activeWebview = newActiveWebview.handle;
} else {
if (this._activeWebview) {
this._proxy.$onDidChangeActiveWeview(undefined);
this._activeWebview = undefined;
}
this._activeWebview = undefined;
}
}
private onDidClickLink(link: URI, options: vscode.WebviewOptions): void {
private onEditorGroupMoved(): void {
for (const workbenchEditor of this._editorService.getVisibleEditors()) {
if (!workbenchEditor.input) {
return;
}
this._webviews.forEach((input, handle) => {
if (workbenchEditor.input.matches(input) && input.position !== workbenchEditor.position) {
input.updatePosition(workbenchEditor.position);
this._proxy.$onDidChangeWebviewPanelViewState(handle, handle === this._activeWebview, workbenchEditor.position);
}
});
}
}
private onDidClickLink(handle: WebviewPanelHandle, link: URI): void {
if (!link) {
return;
}
const enableCommandUris = options.enableCommandUris;
const webview = this.getWebview(handle);
const enableCommandUris = webview.options.enableCommandUris;
if (MainThreadWebviews.standardSupportedLinkSchemes.indexOf(link.scheme) >= 0 || enableCommandUris && link.scheme === 'command') {
this._openerService.open(link);
}
}
}
(<IEditorRegistry>Registry.as(EditorExtensions.Editors)).registerEditor(new EditorDescriptor(
WebviewEditor,
WebviewEditor.ID,
localize('webview.editor.label', "webview editor")),
[new SyncDescriptor(WebviewInput)]);
private static getDeserializationFailedContents(viewType: string) {
return `<!DOCTYPE html>
<html>
<head>
<base href="https://code.visualstudio.com/raw/">
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src https: data:; media-src https:; script-src 'none'; style-src vscode-core-resource: https: 'unsafe-inline'; child-src 'none'; frame-src 'none';">
</head>
<body>${localize('errorMessage', "An error occurred while restoring view:{0}", viewType)}</body>
</html>`;
}
}