Merge from vscode aba87f135229c17c4624341b7a2499dcedafcb87 (#6430)

* Merge from vscode aba87f135229c17c4624341b7a2499dcedafcb87

* fix compile errors
This commit is contained in:
Anthony Dresser
2019-07-18 18:32:57 -07:00
committed by GitHub
parent bf4815d364
commit ee3663c1cd
158 changed files with 3101 additions and 2361 deletions

View File

@@ -7,31 +7,27 @@ import * as DOM from 'vs/base/browser/dom';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Emitter, Event } from 'vs/base/common/event';
import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { EditorOptions } from 'vs/workbench/common/editor';
import { WebviewEditorInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput';
import { IWebviewService, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE, Webview } from 'vs/workbench/contrib/webview/common/webview';
import { KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE, Webview, WebviewEditorOverlay } from 'vs/workbench/contrib/webview/common/webview';
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
export class WebviewEditor extends BaseEditor {
public static readonly ID = 'WebviewEditor';
private _webview: Webview | undefined;
private readonly _scopedContextKeyService = this._register(new MutableDisposable<IContextKeyService>());
private _findWidgetVisible: IContextKey<boolean>;
private _editorFrame: HTMLElement;
private _editorFrame?: HTMLElement;
private _content?: HTMLElement;
private _webviewContent: HTMLElement | undefined;
private readonly _webviewFocusTrackerDisposables = this._register(new DisposableStore());
private readonly _onFocusWindowHandler = this._register(new MutableDisposable());
@@ -39,22 +35,21 @@ export class WebviewEditor extends BaseEditor {
private readonly _onDidFocusWebview = this._register(new Emitter<void>());
public get onDidFocus(): Event<any> { return this._onDidFocusWebview.event; }
private _pendingMessages: any[] = [];
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IThemeService themeService: IThemeService,
@IContextKeyService private _contextKeyService: IContextKeyService,
@IWebviewService private readonly _webviewService: IWebviewService,
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService,
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
@IEditorService private readonly _editorService: IEditorService,
@IWindowService private readonly _windowService: IWindowService,
@IStorageService storageService: IStorageService
) {
super(WebviewEditor.ID, telemetryService, themeService, storageService);
if (_contextKeyService) {
this._findWidgetVisible = KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE.bindTo(_contextKeyService);
}
this._findWidgetVisible = KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE.bindTo(_contextKeyService);
}
public get isWebviewEditor() {
return true;
}
protected createEditor(parent: HTMLElement): void {
@@ -63,58 +58,25 @@ export class WebviewEditor extends BaseEditor {
parent.appendChild(this._content);
}
private doUpdateContainer() {
const webviewContainer = this.input && (this.input as WebviewEditorInput).container;
if (webviewContainer && webviewContainer.parentElement) {
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 dispose(): void {
this._pendingMessages = [];
// Let the editor input dispose of the webview.
this._webview = undefined;
this._webviewContent = undefined;
if (this._content && this._content.parentElement) {
this._content.parentElement.removeChild(this._content);
if (this._content) {
this._content.remove();
this._content = undefined;
}
super.dispose();
}
public sendMessage(data: any): void {
if (this._webview) {
this._webview.sendMessage(data);
} else {
this._pendingMessages.push(data);
}
}
public showFind() {
if (this._webview) {
this._webview.showFind();
this.withWebview(webview => {
webview.showFind();
this._findWidgetVisible.set(true);
}
});
}
public hideFind() {
this._findWidgetVisible.reset();
if (this._webview) {
this._webview.hideFind();
}
}
public get isWebviewEditor() {
return true;
this.withWebview(webview => webview.hideFind());
}
public reload() {
@@ -122,16 +84,15 @@ export class WebviewEditor extends BaseEditor {
}
public layout(_dimension: DOM.Dimension): void {
this.withWebview(webview => {
this.doUpdateContainer();
webview.layout();
});
if (this.input && this.input instanceof WebviewEditorInput) {
this.synchronizeWebviewContainerDimensions(this.input.webview);
this.input.webview.layout();
}
}
public focus(): void {
super.focus();
if (!this._onFocusWindowHandler.value) {
// Make sure we restore focus when switching back to a VS Code window
this._onFocusWindowHandler.value = this._windowService.onDidChangeFocus(focused => {
if (focused && this._editorService.activeControl === this) {
@@ -143,29 +104,20 @@ export class WebviewEditor extends BaseEditor {
}
public withWebview(f: (element: Webview) => void): void {
if (this._webview) {
f(this._webview);
if (this.input && this.input instanceof WebviewEditorInput) {
f(this.input.webview);
}
}
protected setEditorVisible(visible: boolean, group: IEditorGroup): void {
if (this.input && this.input instanceof WebviewEditorInput) {
const webview = this.input && (this.input as WebviewEditorInput).webview;
if (webview) {
if (visible) {
this.input.claimWebview(this);
webview.claim(this);
} else {
this.input.releaseWebview(this);
}
this.updateWebview(this.input as WebviewEditorInput);
}
if (this._webviewContent) {
if (visible) {
this._webviewContent.style.visibility = 'visible';
this.doUpdateContainer();
} else {
this._webviewContent.style.visibility = 'hidden';
webview.release(this);
}
this.claimWebview(this.input as WebviewEditorInput);
}
super.setEditorVisible(visible, group);
@@ -173,115 +125,69 @@ export class WebviewEditor extends BaseEditor {
public clearInput() {
if (this.input && this.input instanceof WebviewEditorInput) {
this.input.releaseWebview(this);
this.input.webview.release(this);
}
this._webview = undefined;
this._webviewContent = undefined;
this._pendingMessages = [];
super.clearInput();
}
setInput(input: WebviewEditorInput, options: EditorOptions, token: CancellationToken): Promise<void> {
if (this.input) {
(this.input as WebviewEditorInput).releaseWebview(this);
this._webview = undefined;
this._webviewContent = undefined;
public async setInput(input: WebviewEditorInput, options: EditorOptions, token: CancellationToken): Promise<void> {
if (this.input && this.input instanceof WebviewEditorInput) {
this.input.webview.release(this);
}
this._pendingMessages = [];
return super.setInput(input, options, token)
.then(() => input.resolve())
.then(() => {
if (token.isCancellationRequested) {
return;
}
if (this.group) {
input.updateGroup(this.group.id);
}
this.updateWebview(input);
});
await super.setInput(input, options, token);
await input.resolve();
if (token.isCancellationRequested) {
return;
}
if (this.group) {
input.updateGroup(this.group.id);
}
this.claimWebview(input);
}
private updateWebview(input: WebviewEditorInput) {
const webview = this.getWebview(input);
input.claimWebview(this);
webview.update(input.html, {
allowScripts: input.options.enableScripts,
localResourceRoots: input.options.localResourceRoots || this.getDefaultLocalResourceRoots(),
portMappings: input.options.portMapping,
}, !!input.options.retainContextWhenHidden);
private claimWebview(input: WebviewEditorInput): void {
input.webview.claim(this);
if (this._webviewContent) {
this._webviewContent.style.visibility = 'visible';
if (input.webview.options.enableFindWidget) {
this._scopedContextKeyService.value = this._contextKeyService.createScoped(input.webview.container);
this._findWidgetVisible = KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE.bindTo(this._scopedContextKeyService.value);
}
this.doUpdateContainer();
if (this._content) {
this._content.setAttribute('aria-flowto', input.webview.container.id);
}
this.synchronizeWebviewContainerDimensions(input.webview);
this.trackFocus(input.webview);
}
private getDefaultLocalResourceRoots(): URI[] {
const rootPaths = this._contextService.getWorkspace().folders.map(x => x.uri);
const extension = (this.input as WebviewEditorInput).extension;
if (extension) {
rootPaths.push(extension.location);
private synchronizeWebviewContainerDimensions(webview: WebviewEditorOverlay) {
const webviewContainer = webview.container;
if (webviewContainer && webviewContainer.parentElement && this._editorFrame) {
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`;
}
return rootPaths;
}
private getWebview(input: WebviewEditorInput): Webview {
if (this._webview) {
return this._webview;
}
this._webviewContent = input.container;
if (input.webview) {
this._webview = input.webview;
} else {
if (input.options.enableFindWidget) {
this._contextKeyService = this._register(this._contextKeyService.createScoped(this._webviewContent));
this._findWidgetVisible = KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE.bindTo(this._contextKeyService);
}
this._webview = this._webviewService.createWebview(input.id,
{
allowSvgs: true,
extension: input.extension,
enableFindWidget: input.options.enableFindWidget
}, {});
this._webview.mountTo(this._webviewContent);
input.webview = this._webview;
if (input.options.tryRestoreScrollPosition) {
this._webview.initialScrollProgress = input.scrollYPercentage;
}
this._webview.state = input.state ? input.state.state : undefined;
this._content!.setAttribute('aria-flowto', this._webviewContent.id);
this.doUpdateContainer();
}
for (const message of this._pendingMessages) {
this._webview.sendMessage(message);
}
this._pendingMessages = [];
this.trackFocus();
return this._webview;
}
private trackFocus() {
private trackFocus(webview: WebviewEditorOverlay): void {
this._webviewFocusTrackerDisposables.clear();
// Track focus in webview content
const webviewContentFocusTracker = DOM.trackFocus(this._webviewContent!);
const webviewContentFocusTracker = DOM.trackFocus(webview.container);
this._webviewFocusTrackerDisposables.add(webviewContentFocusTracker);
this._webviewFocusTrackerDisposables.add(webviewContentFocusTracker.onDidFocus(() => this._onDidFocusWebview.fire()));
// Track focus in webview element
this._webviewFocusTrackerDisposables.add(this._webview!.onDidFocus(() => this._onDidFocusWebview.fire()));
this._webviewFocusTrackerDisposables.add(webview.onDidFocus(() => this._onDidFocusWebview.fire()));
}
}

View File

@@ -4,15 +4,11 @@
*--------------------------------------------------------------------------------------------*/
import * as dom from 'vs/base/browser/dom';
import { memoize } from 'vs/base/common/decorators';
import { Emitter } from 'vs/base/common/event';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { IEditorModel } from 'vs/platform/editor/common/editor';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { EditorInput, EditorModel, GroupIdentifier, IEditorInput } from 'vs/workbench/common/editor';
import { Webview, WebviewOptions } from 'vs/workbench/contrib/webview/common/webview';
import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService';
import { WebviewEvents, WebviewInputOptions } from './webviewEditorService';
import { WebviewEditorOverlay } from 'vs/workbench/contrib/webview/common/webview';
class WebviewIconsManager {
private readonly _icons = new Map<string, { light: URI, dark: URI }>();
@@ -53,77 +49,38 @@ class WebviewIconsManager {
}
}
export class WebviewEditorInput<State = any> extends EditorInput {
private readonly iconsManager = new WebviewIconsManager();
export class WebviewEditorInput extends EditorInput {
public static readonly typeId = 'workbench.editors.webviewInput';
private static readonly iconsManager = new WebviewIconsManager();
private _name: string;
private _iconPath?: { light: URI, dark: URI };
private _options: WebviewInputOptions;
private _html: string = '';
private _currentWebviewHtml: string = '';
public _events: WebviewEvents | undefined;
private _container?: HTMLElement;
private _webview?: Webview;
private _webviewOwner: any;
private readonly _webviewDisposables = this._register(new DisposableStore());
private _group?: GroupIdentifier;
private _scrollYPercentage: number = 0;
private _state: State;
public readonly extension?: {
readonly location: URI;
readonly id: ExtensionIdentifier;
};
constructor(
public readonly id: string,
public readonly viewType: string,
name: string,
options: WebviewInputOptions,
state: State,
events: WebviewEvents,
extension: undefined | {
public readonly extension: undefined | {
readonly location: URI;
readonly id: ExtensionIdentifier;
},
@IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService,
public readonly webview: WebviewEditorOverlay,
) {
super();
this._name = name;
this._options = options;
this._events = events;
this._state = state;
this.extension = extension;
this._register(webview); // The input owns this webview
}
public getTypeId(): string {
return WebviewEditorInput.typeId;
}
private readonly _onDidChangeIcon = this._register(new Emitter<void>());
public readonly onDidChangeIcon = this._onDidChangeIcon.event;
public dispose() {
this.disposeWebview();
if (this._container) {
this._container.remove();
this._container = undefined;
}
if (this._events && this._events.onDispose) {
this._events.onDispose();
}
this._events = undefined;
this._webview = undefined;
super.dispose();
}
public getResource(): URI {
return URI.from({
scheme: 'webview-panel',
@@ -154,7 +111,7 @@ export class WebviewEditorInput<State = any> extends EditorInput {
public set iconPath(value: { light: URI, dark: URI } | undefined) {
this._iconPath = value;
this.iconsManager.setIcons(this.id, value);
WebviewEditorInput.iconsManager.setIcons(this.id, value);
}
public matches(other: IEditorInput): boolean {
@@ -165,145 +122,19 @@ export class WebviewEditorInput<State = any> extends EditorInput {
return this._group;
}
public get html(): string {
return this._html;
}
public set html(value: string) {
if (value === this._currentWebviewHtml) {
return;
}
this._html = value;
if (this._webview) {
this._webview.html = value;
this._currentWebviewHtml = value;
}
}
public get state(): State {
return this._state;
}
public set state(value: State) {
this._state = value;
}
public get options(): WebviewInputOptions {
return this._options;
}
public setOptions(value: WebviewOptions) {
this._options = {
...this._options,
...value
};
if (this._webview) {
this._webview.options = {
allowScripts: this._options.enableScripts,
localResourceRoots: this._options.localResourceRoots,
portMappings: this._options.portMapping,
};
}
}
public resolve(): Promise<IEditorModel> {
return Promise.resolve(new EditorModel());
public async resolve(): Promise<IEditorModel> {
return new EditorModel();
}
public supportsSplitEditor() {
return false;
}
public get container(): HTMLElement {
if (!this._container) {
this._container = document.createElement('div');
this._container.id = `webview-${this.id}`;
const part = this._layoutService.getContainer(Parts.EDITOR_PART);
part.appendChild(this._container);
}
return this._container;
}
public get webview(): Webview | undefined {
return this._webview;
}
public set webview(value: Webview | undefined) {
this._webviewDisposables.clear();
this._webview = value;
if (!this._webview) {
return;
}
this._webview.onDidClickLink(link => {
if (this._events && this._events.onDidClickLink) {
this._events.onDidClickLink(link, this._options);
}
}, null, this._webviewDisposables);
this._webview.onMessage(message => {
if (this._events && this._events.onMessage) {
this._events.onMessage(message);
}
}, null, this._webviewDisposables);
this._webview.onDidScroll(message => {
this._scrollYPercentage = message.scrollYPercentage;
}, null, this._webviewDisposables);
this._webview.onDidUpdateState(newState => {
if (this._events && this._events.onDidUpdateWebviewState) {
this._events.onDidUpdateWebviewState(newState);
}
}, null, this._webviewDisposables);
}
public get scrollYPercentage() {
return this._scrollYPercentage;
}
public claimWebview(owner: any) {
this._webviewOwner = owner;
}
public releaseWebview(owner: any) {
if (this._webviewOwner === owner) {
this._webviewOwner = undefined;
if (this._options.retainContextWhenHidden && this._container) {
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.clear();
this._webviewOwner = undefined;
if (this._container) {
this._container.style.visibility = 'hidden';
}
this._currentWebviewHtml = '';
}
public updateGroup(group: GroupIdentifier): void {
this._group = group;
}
}
export class RevivedWebviewEditorInput extends WebviewEditorInput {
private _revived: boolean = false;
@@ -311,17 +142,14 @@ export class RevivedWebviewEditorInput extends WebviewEditorInput {
id: string,
viewType: string,
name: string,
options: WebviewInputOptions,
state: any,
events: WebviewEvents,
extension: undefined | {
readonly location: URI;
readonly id: ExtensionIdentifier
},
private readonly reviver: (input: WebviewEditorInput) => Promise<void>,
@IWorkbenchLayoutService partService: IWorkbenchLayoutService,
webview: WebviewEditorOverlay,
) {
super(id, viewType, name, options, state, events, extension, partService);
super(id, viewType, name, extension, webview);
}
public async resolve(): Promise<IEditorModel> {

View File

@@ -45,10 +45,10 @@ export class WebviewEditorInputFactory implements IEditorInputFactory {
const data: SerializedWebview = {
viewType: input.viewType,
title: input.getName(),
options: input.options,
options: input.webview.options,
extensionLocation: input.extension ? input.extension.location : undefined,
extensionId: input.extension && input.extension.id ? input.extension.id.value : undefined,
state: input.state,
state: input.webview.state,
iconPath: input.iconPath ? { light: input.iconPath.light, dark: input.iconPath.dark, } : undefined,
group: input.group
};
@@ -68,12 +68,15 @@ export class WebviewEditorInputFactory implements IEditorInputFactory {
const extensionLocation = reviveUri(data.extensionLocation);
const extensionId = data.extensionId ? new ExtensionIdentifier(data.extensionId) : undefined;
const iconPath = reviveIconPath(data.iconPath);
return this._webviewService.reviveWebview(generateUuid(), data.viewType, data.title, iconPath, data.state, data.options, extensionLocation ? {
const state = reviveState(data.state);
return this._webviewService.reviveWebview(generateUuid(), data.viewType, data.title, iconPath, state, data.options, extensionLocation ? {
location: extensionLocation,
id: extensionId
} : undefined, data.group);
}
}
function reviveIconPath(data: SerializedIconPath | undefined) {
if (!data) {
return undefined;
@@ -98,3 +101,21 @@ function reviveUri(data: string | UriComponents | undefined): URI | undefined {
return undefined;
}
}
function reviveState(state: unknown | undefined): undefined | string {
if (!state) {
return undefined;
}
if (typeof state === 'string') {
return state;
}
// Likely an old style state. Unwrap to a simple state object
// Remove after 1.37
if ('state' in (state as any) && typeof (state as any).state === 'string') {
return (state as any).state;
}
return undefined;
}

View File

@@ -7,13 +7,14 @@ import { equals } from 'vs/base/common/arrays';
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { values } from 'vs/base/common/map';
import { URI } from 'vs/base/common/uri';
import { IWebviewOptions, IWebviewPanelOptions } from 'vs/editor/common/modes';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { GroupIdentifier } from 'vs/workbench/common/editor';
import { IWebviewService, WebviewOptions, WebviewContentOptions } from 'vs/workbench/contrib/webview/common/webview';
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { ACTIVE_GROUP_TYPE, IEditorService, SIDE_GROUP_TYPE } from 'vs/workbench/services/editor/common/editorService';
import { RevivedWebviewEditorInput, WebviewEditorInput } from './webviewEditorInput';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
export const IWebviewEditorService = createDecorator<IWebviewEditorService>('webviewEditorService');
@@ -35,7 +36,6 @@ export interface IWebviewEditorService {
location: URI,
id: ExtensionIdentifier
},
events: WebviewEvents
): WebviewEditorInput;
reviveWebview(
@@ -77,25 +77,20 @@ export interface WebviewReviver {
): Promise<void>;
}
export interface WebviewEvents {
onMessage?(message: any): void;
onDispose?(): void;
onDidClickLink?(link: URI, options: IWebviewOptions): void;
onDidUpdateWebviewState?(newState: any): void;
}
export interface WebviewInputOptions extends IWebviewOptions, IWebviewPanelOptions {
tryRestoreScrollPosition?: boolean;
export interface WebviewInputOptions extends WebviewOptions, WebviewContentOptions {
readonly tryRestoreScrollPosition?: boolean;
readonly retainContextWhenHidden?: boolean;
readonly enableCommandUris?: boolean;
}
export function areWebviewInputOptionsEqual(a: WebviewInputOptions, b: WebviewInputOptions): boolean {
return a.enableCommandUris === b.enableCommandUris
&& a.enableFindWidget === b.enableFindWidget
&& a.enableScripts === b.enableScripts
&& a.allowScripts === b.allowScripts
&& a.retainContextWhenHidden === b.retainContextWhenHidden
&& a.tryRestoreScrollPosition === b.tryRestoreScrollPosition
&& (a.localResourceRoots === b.localResourceRoots || (Array.isArray(a.localResourceRoots) && Array.isArray(b.localResourceRoots) && equals(a.localResourceRoots, b.localResourceRoots, (a, b) => a.toString() === b.toString())))
&& (a.portMapping === b.portMapping || (Array.isArray(a.portMapping) && Array.isArray(b.portMapping) && equals(a.portMapping, b.portMapping, (a, b) => a.extensionHostPort === b.extensionHostPort && a.webviewPort === b.webviewPort)));
&& (a.portMappings === b.portMappings || (Array.isArray(a.portMappings) && Array.isArray(b.portMappings) && equals(a.portMappings, b.portMappings, (a, b) => a.extensionHostPort === b.extensionHostPort && a.webviewPort === b.webviewPort)));
}
function canRevive(reviver: WebviewReviver, webview: WebviewEditorInput): boolean {
@@ -132,6 +127,8 @@ export class WebviewEditorService implements IWebviewEditorService {
@IEditorService private readonly _editorService: IEditorService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService,
@IWebviewService private readonly _webviewService: IWebviewService,
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService,
) { }
public createWebview(
@@ -139,14 +136,15 @@ export class WebviewEditorService implements IWebviewEditorService {
viewType: string,
title: string,
showOptions: ICreateWebViewShowOptions,
options: IWebviewOptions,
options: WebviewInputOptions,
extension: undefined | {
location: URI,
id: ExtensionIdentifier
},
events: WebviewEvents
): WebviewEditorInput {
const webviewInput = this._instantiationService.createInstance(WebviewEditorInput, id, viewType, title, options, {}, events, extension);
const webview = this.createWebiew(id, extension, options);
const webviewInput = this._instantiationService.createInstance(WebviewEditorInput, id, viewType, title, extension, webview);
this._editorService.openEditor(webviewInput, { pinned: true, preserveFocus: showOptions.preserveFocus }, showOptions.group);
return webviewInput;
}
@@ -175,11 +173,14 @@ export class WebviewEditorService implements IWebviewEditorService {
options: WebviewInputOptions,
extension: undefined | {
readonly location: URI,
readonly id?: ExtensionIdentifier
readonly id: ExtensionIdentifier
},
group: number | undefined,
): WebviewEditorInput {
const webviewInput = this._instantiationService.createInstance(RevivedWebviewEditorInput, id, viewType, title, options, state, {}, extension, async (webview: WebviewEditorInput): Promise<void> => {
const webview = this.createWebiew(id, extension, options);
webview.state = state;
const webviewInput = new RevivedWebviewEditorInput(id, viewType, title, extension, async (webview: WebviewEditorInput): Promise<void> => {
const didRevive = await this.tryRevive(webview);
if (didRevive) {
return Promise.resolve(undefined);
@@ -190,8 +191,10 @@ export class WebviewEditorService implements IWebviewEditorService {
const promise = new Promise<void>(r => { resolve = r; });
this._revivalPool.add(webview, resolve!);
return promise;
});
}, webview);
webviewInput.iconPath = iconPath;
if (typeof group === 'number') {
webviewInput.updateGroup(group);
}
@@ -213,7 +216,7 @@ export class WebviewEditorService implements IWebviewEditorService {
webview: WebviewEditorInput
): boolean {
// Has no state, don't persist
if (!webview.state) {
if (!webview.webview.state) {
return false;
}
@@ -237,4 +240,27 @@ export class WebviewEditorService implements IWebviewEditorService {
}
return false;
}
private createWebiew(id: string, extension: { location: URI; id: ExtensionIdentifier; } | undefined, options: WebviewInputOptions) {
return this._webviewService.createWebviewEditorOverlay(id, {
allowSvgs: true,
extension: extension,
enableFindWidget: options.enableFindWidget,
retainContextWhenHidden: options.retainContextWhenHidden
}, {
...options,
localResourceRoots: options.localResourceRoots || this.getDefaultLocalResourceRoots(extension),
});
}
private getDefaultLocalResourceRoots(extension: undefined | {
location: URI,
id: ExtensionIdentifier
}): URI[] {
const rootPaths = this._contextService.getWorkspace().folders.map(x => x.uri);
if (extension) {
rootPaths.push(extension.location);
}
return rootPaths;
}
}

View File

@@ -155,7 +155,7 @@ export class IFrameWebview extends Disposable implements Webview {
}
}
public set options(options: WebviewContentOptions) {
public set contentOptions(options: WebviewContentOptions) {
if (areWebviewInputOptionsEqual(options, this.content.options)) {
return;
}

View File

@@ -3,9 +3,14 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IFrameWebview as WebviewElement } from 'vs/workbench/contrib/webview/browser/webviewElement';
import { IWebviewService, WebviewOptions, WebviewContentOptions, Webview } from 'vs/workbench/contrib/webview/common/webview';
import { IFrameWebview } from 'vs/workbench/contrib/webview/browser/webviewElement';
import { IWebviewService, Webview, WebviewContentOptions, WebviewEditorOverlay, WebviewElement, WebviewOptions } from 'vs/workbench/contrib/webview/common/webview';
import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService';
import { memoize } from 'vs/base/common/decorators';
export class WebviewService implements IWebviewService {
_serviceBrand: any;
@@ -18,10 +23,178 @@ export class WebviewService implements IWebviewService {
id: string,
options: WebviewOptions,
contentOptions: WebviewContentOptions
): Webview {
return this._instantiationService.createInstance(WebviewElement,
id,
options,
contentOptions);
): WebviewElement {
return this._instantiationService.createInstance(IFrameWebview, id, options, contentOptions);
}
createWebviewEditorOverlay(
id: string,
options: WebviewOptions,
contentOptions: WebviewContentOptions,
): WebviewEditorOverlay {
return this._instantiationService.createInstance(DynamicWebviewEditorOverlay, id, options, contentOptions);
}
}
/**
* Webview editor overlay that creates and destroys the underlying webview as needed.
*/
class DynamicWebviewEditorOverlay extends Disposable implements WebviewEditorOverlay {
private readonly _pendingMessages = new Set<any>();
private readonly _webview = this._register(new MutableDisposable<WebviewElement>());
private readonly _webviewEvents = this._register(new DisposableStore());
private _html: string = '';
private _initialScrollProgress: number = 0;
private _state: string | undefined = undefined;
private _owner: any = undefined;
public constructor(
private readonly id: string,
public readonly options: WebviewOptions,
private _contentOptions: WebviewContentOptions,
@IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService,
@IWebviewService private readonly _webviewService: IWebviewService,
) {
super();
this._register(toDisposable(() => this.container.remove()));
}
@memoize
public get container() {
const container = document.createElement('div');
container.id = `webview-${this.id}`;
this._layoutService.getContainer(Parts.EDITOR_PART).appendChild(container);
return container;
}
public claim(owner: any) {
this._owner = owner;
this.show();
}
public release(owner: any) {
if (this._owner !== owner) {
return;
}
this._owner = undefined;
this.container.style.visibility = 'hidden';
if (!this.options.retainContextWhenHidden) {
this._webview.clear();
this._webviewEvents.clear();
}
}
private show() {
if (!this._webview.value) {
const webview = this._webviewService.createWebview(this.id, this.options, this._contentOptions);
this._webview.value = webview;
webview.state = this._state;
webview.html = this._html;
if (this.options.tryRestoreScrollPosition) {
webview.initialScrollProgress = this._initialScrollProgress;
}
this._webview.value.mountTo(this.container);
this._webviewEvents.clear();
webview.onDidFocus(() => {
this._onDidFocus.fire();
}, undefined, this._webviewEvents);
webview.onDidClickLink(x => {
this._onDidClickLink.fire(x);
}, undefined, this._webviewEvents);
webview.onDidScroll(x => {
this._initialScrollProgress = x.scrollYPercentage;
this._onDidScroll.fire(x);
}, undefined, this._webviewEvents);
webview.onDidUpdateState(state => {
this._state = state;
this._onDidUpdateState.fire(state);
}, undefined, this._webviewEvents);
webview.onMessage(x => {
this._onMessage.fire(x);
}, undefined, this._webviewEvents);
this._pendingMessages.forEach(msg => webview.sendMessage(msg));
this._pendingMessages.clear();
}
this.container.style.visibility = 'visible';
}
public get html(): string { return this._html; }
public set html(value: string) {
this._html = value;
this.withWebview(webview => webview.html = value);
}
public get initialScrollProgress(): number { return this._initialScrollProgress; }
public set initialScrollProgress(value: number) {
this._initialScrollProgress = value;
this.withWebview(webview => webview.initialScrollProgress = value);
}
public get state(): string | undefined { return this._state; }
public set state(value: string | undefined) {
this._state = value;
this.withWebview(webview => webview.state = value);
}
public get contentOptions(): WebviewContentOptions { return this._contentOptions; }
public set contentOptions(value: WebviewContentOptions) {
this._contentOptions = value;
this.withWebview(webview => webview.contentOptions = value);
}
private readonly _onDidFocus = this._register(new Emitter<void>());
public readonly onDidFocus: Event<void> = this._onDidFocus.event;
private readonly _onDidClickLink = this._register(new Emitter<URI>());
public readonly onDidClickLink: Event<URI> = this._onDidClickLink.event;
private readonly _onDidScroll = this._register(new Emitter<{ scrollYPercentage: number; }>());
public readonly onDidScroll: Event<{ scrollYPercentage: number; }> = this._onDidScroll.event;
private readonly _onDidUpdateState = this._register(new Emitter<string | undefined>());
public readonly onDidUpdateState: Event<string | undefined> = this._onDidUpdateState.event;
private readonly _onMessage = this._register(new Emitter<any>());
public readonly onMessage: Event<any> = this._onMessage.event;
sendMessage(data: any): void {
if (this._webview.value) {
this._webview.value.sendMessage(data);
} else {
this._pendingMessages.add(data);
}
}
update(html: string, options: WebviewContentOptions, retainContextWhenHidden: boolean): void {
this._contentOptions = options;
this._html = html;
this.withWebview(webview => {
webview.update(html, options, retainContextWhenHidden);
});
}
layout(): void { this.withWebview(webview => webview.layout()); }
focus(): void { this.withWebview(webview => webview.focus()); }
reload(): void { this.withWebview(webview => webview.reload()); }
showFind(): void { this.withWebview(webview => webview.showFind()); }
hideFind(): void { this.withWebview(webview => webview.hideFind()); }
private withWebview(f: (webview: Webview) => void): void {
if (this._webview.value) {
f(this._webview.value);
}
}
}

View File

@@ -7,10 +7,10 @@ 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 * as nls from 'vs/nls';
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 * as nls from 'vs/nls';
/**
* Set when the find widget in a webview is visible.
@@ -29,7 +29,13 @@ export interface IWebviewService {
id: string,
options: WebviewOptions,
contentOptions: WebviewContentOptions,
): Webview;
): WebviewElement;
createWebviewEditorOverlay(
id: string,
options: WebviewOptions,
contentOptions: WebviewContentOptions,
): WebviewEditorOverlay;
}
export const WebviewResourceScheme = 'vscode-resource';
@@ -41,6 +47,8 @@ export interface WebviewOptions {
readonly id?: ExtensionIdentifier;
};
readonly enableFindWidget?: boolean;
readonly tryRestoreScrollPosition?: boolean;
readonly retainContextWhenHidden?: boolean;
}
export interface WebviewContentOptions {
@@ -48,12 +56,13 @@ export interface WebviewContentOptions {
readonly svgWhiteList?: string[];
readonly localResourceRoots?: ReadonlyArray<URI>;
readonly portMappings?: ReadonlyArray<modes.IWebviewPortMapping>;
readonly enableCommandUris?: boolean;
}
export interface Webview extends IDisposable {
html: string;
options: WebviewContentOptions;
contentOptions: WebviewContentOptions;
initialScrollProgress: number;
state: string | undefined;
@@ -65,13 +74,12 @@ export interface Webview extends IDisposable {
sendMessage(data: any): void;
update(
value: string,
html: string,
options: WebviewContentOptions,
retainContextWhenHidden: boolean
): void;
layout(): void;
mountTo(parent: HTMLElement): void;
focus(): void;
reload(): void;
@@ -79,4 +87,16 @@ export interface Webview extends IDisposable {
hideFind(): void;
}
export interface WebviewElement extends Webview {
mountTo(parent: HTMLElement): void;
}
export interface WebviewEditorOverlay extends Webview {
readonly container: HTMLElement;
readonly options: WebviewOptions;
claim(owner: any): void;
release(owner: any): void;
}
export const webviewDeveloperCategory = nls.localize('developer', "Developer");

View File

@@ -8,7 +8,7 @@ import * as nls from 'vs/nls';
import { Command, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { WebviewEditor } from 'vs/workbench/contrib/webview/browser/webviewEditor';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { WebviewElement } from 'vs/workbench/contrib/webview/electron-browser/webviewElement';
import { ElectronWebviewBasedWebview } from 'vs/workbench/contrib/webview/electron-browser/webviewElement';
export class OpenWebviewDeveloperToolsAction extends Action {
static readonly ID = 'workbench.action.webview.openDeveloperTools';
@@ -86,11 +86,11 @@ function getActiveWebviewEditor(accessor: ServicesAccessor): WebviewEditor | und
return activeControl.isWebviewEditor ? activeControl : undefined;
}
function withActiveWebviewBasedWebview(accessor: ServicesAccessor, f: (webview: WebviewElement) => void): void {
function withActiveWebviewBasedWebview(accessor: ServicesAccessor, f: (webview: ElectronWebviewBasedWebview) => void): void {
const webViewEditor = getActiveWebviewEditor(accessor);
if (webViewEditor) {
webViewEditor.withWebview(webview => {
if (webview instanceof WebviewElement) {
if (webview instanceof ElectronWebviewBasedWebview) {
f(webview);
}
});

View File

@@ -13,7 +13,6 @@ import { endsWith } from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
import * as modes from 'vs/editor/common/modes';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { IFileService } from 'vs/platform/files/common/files';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ITunnelService } from 'vs/platform/remote/common/tunnel';
@@ -25,27 +24,6 @@ import { registerFileProtocol } from 'vs/workbench/contrib/webview/electron-brow
import { areWebviewInputOptionsEqual } from '../browser/webviewEditorService';
import { WebviewFindWidget } from '../browser/webviewFindWidget';
export interface WebviewPortMapping {
readonly port: number;
readonly resolvedPort: number;
}
export interface WebviewOptions {
readonly allowSvgs?: boolean;
readonly extension?: {
readonly location: URI;
readonly id?: ExtensionIdentifier;
};
readonly enableFindWidget?: boolean;
}
export interface WebviewContentOptions {
readonly allowScripts?: boolean;
readonly svgWhiteList?: string[];
readonly localResourceRoots?: ReadonlyArray<URI>;
readonly portMappings?: ReadonlyArray<WebviewPortMapping>;
}
interface IKeydownEvent {
key: string;
keyCode: number;
@@ -285,7 +263,7 @@ interface WebviewContent {
readonly state: string | undefined;
}
export class WebviewElement extends Disposable implements Webview {
export class ElectronWebviewBasedWebview extends Disposable implements Webview {
private _webview: Electron.WebviewTag | undefined;
private _ready: Promise<void>;
@@ -508,7 +486,7 @@ export class WebviewElement extends Disposable implements Webview {
};
}
public set options(options: WebviewContentOptions) {
public set contentOptions(options: WebviewContentOptions) {
if (areWebviewInputOptionsEqual(options, this.content.options)) {
return;
}

View File

@@ -4,23 +4,24 @@
*--------------------------------------------------------------------------------------------*/
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IWebviewService, Webview, WebviewContentOptions, WebviewOptions } from 'vs/workbench/contrib/webview/common/webview';
import { WebviewElement } from 'vs/workbench/contrib/webview/electron-browser/webviewElement';
import { WebviewService as BrowserWebviewService } from 'vs/workbench/contrib/webview/browser/webviewService';
import { IWebviewService, WebviewContentOptions, WebviewElement, WebviewOptions } from 'vs/workbench/contrib/webview/common/webview';
import { ElectronWebviewBasedWebview } from 'vs/workbench/contrib/webview/electron-browser/webviewElement';
export class WebviewService implements IWebviewService {
export class WebviewService extends BrowserWebviewService implements IWebviewService {
_serviceBrand: any;
constructor(
@IInstantiationService private readonly _instantiationService: IInstantiationService,
) { }
@IInstantiationService private readonly instantiationService: IInstantiationService,
) {
super(instantiationService);
}
createWebview(
_id: string,
options: WebviewOptions,
contentOptions: WebviewContentOptions
): Webview {
return this._instantiationService.createInstance(WebviewElement,
options,
contentOptions);
): WebviewElement {
return this.instantiationService.createInstance(ElectronWebviewBasedWebview, options, contentOptions);
}
}