mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-26 11:38:51 +01:00
Webview API prototype 3 (#44307)
* Webview API prototype 3 Part of #43713 Third try at refining the webview api. This pass reworks #44165. Major changes: - Adds an `id` field to webviews. The id is provided by the extension and identifies the webview. It is used with the new event handling apis. - Adds a new `onDidChangeActiveEditor` api. This is similar to `onDidChangeActiveTextEditor` but is also fired when you change webviews. It replaces the old `onFocus` and `onBlur` events on the webview itself - Adds an `onDispose` event ot webviews. This is fired when a webview is closed by the user - Perist webview state when the editor group changes. This is enabled for all webviews, not just those with keep alive. * Throw error when trying to access disposed webview * Improving webview documentation * Clean up dispose management * Throw if we receive a bad handle * Move more event handling to input * Simplify input updating * Remove extra container property * Fixing md security alert button * Remove extra update container call * Restore syncing of preview to active editor * Fixing posting to webview * Debounce preview updates * Remove previewUri * Enable direct window.postMessage instead of window.parent.postMessage * Fixing scroll position not preserved when updating previews * Revert parent.postMessage change. Old behavior was correct * Properly hide webview container on tab switch * Make sure we only handle scroll events for the correct document * Don't try setting negative scroll * Revert vs code whitespace change
This commit is contained in:
@@ -97,7 +97,8 @@ export function createApiFactory(
|
||||
rpcProtocol.set(ExtHostContext.ExtHostLogService, extHostLogService);
|
||||
const extHostHeapService = rpcProtocol.set(ExtHostContext.ExtHostHeapService, new ExtHostHeapService());
|
||||
const extHostDecorations = rpcProtocol.set(ExtHostContext.ExtHostDecorations, new ExtHostDecorations(rpcProtocol));
|
||||
const extHostDocumentsAndEditors = rpcProtocol.set(ExtHostContext.ExtHostDocumentsAndEditors, new ExtHostDocumentsAndEditors(rpcProtocol));
|
||||
const extHostWebviews = rpcProtocol.set(ExtHostContext.ExtHostWebviews, new ExtHostWebviews(rpcProtocol));
|
||||
const extHostDocumentsAndEditors = rpcProtocol.set(ExtHostContext.ExtHostDocumentsAndEditors, new ExtHostDocumentsAndEditors(rpcProtocol, extHostWebviews));
|
||||
const extHostDocuments = rpcProtocol.set(ExtHostContext.ExtHostDocuments, new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors));
|
||||
const extHostDocumentContentProviders = rpcProtocol.set(ExtHostContext.ExtHostDocumentContentProviders, new ExtHostDocumentContentProvider(rpcProtocol, extHostDocumentsAndEditors));
|
||||
const extHostDocumentSaveParticipant = rpcProtocol.set(ExtHostContext.ExtHostDocumentSaveParticipant, new ExtHostDocumentSaveParticipant(extHostLogService, extHostDocuments, rpcProtocol.getProxy(MainContext.MainThreadTextEditors)));
|
||||
@@ -117,7 +118,6 @@ export function createApiFactory(
|
||||
const extHostTask = rpcProtocol.set(ExtHostContext.ExtHostTask, new ExtHostTask(rpcProtocol, extHostWorkspace));
|
||||
const extHostWindow = rpcProtocol.set(ExtHostContext.ExtHostWindow, new ExtHostWindow(rpcProtocol));
|
||||
rpcProtocol.set(ExtHostContext.ExtHostExtensionService, extensionService);
|
||||
const extHostWebviews = rpcProtocol.set(ExtHostContext.ExtHostWebviews, new ExtHostWebviews(rpcProtocol));
|
||||
|
||||
// Check that no named customers are missing
|
||||
const expected: ProxyIdentifier<any>[] = Object.keys(ExtHostContext).map((key) => ExtHostContext[key]);
|
||||
@@ -402,8 +402,11 @@ export function createApiFactory(
|
||||
registerDecorationProvider: proposedApiFunction(extension, (provider: vscode.DecorationProvider) => {
|
||||
return extHostDecorations.registerDecorationProvider(provider, extension.id);
|
||||
}),
|
||||
createWebview: proposedApiFunction(extension, (name: string, column: vscode.ViewColumn, options: vscode.WebviewOptions) => {
|
||||
return extHostWebviews.createWebview(name, column, options);
|
||||
createWebview: proposedApiFunction(extension, (uri: vscode.Uri, column: vscode.ViewColumn, options: vscode.WebviewOptions) => {
|
||||
return extHostWebviews.createWebview(uri, column, options);
|
||||
}),
|
||||
onDidChangeActiveEditor: proposedApiFunction(extension, (listener, thisArg?, disposables?) => {
|
||||
return extHostDocumentsAndEditors.onDidChangeActiveEditor(listener, thisArg, disposables);
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
@@ -342,19 +342,21 @@ export interface MainThreadTelemetryShape extends IDisposable {
|
||||
$publicLog(eventName: string, data?: any): void;
|
||||
}
|
||||
|
||||
export interface MainThreadWebviewShape extends IDisposable {
|
||||
$createWebview(handle: number): void;
|
||||
$disposeWebview(handle: number): void;
|
||||
$show(handle: number, column: EditorPosition): void;
|
||||
$setTitle(handle: number, value: string): void;
|
||||
$setHtml(handle: number, value: string): void;
|
||||
$setOptions(handle: number, value: vscode.WebviewOptions): void;
|
||||
$sendMessage(handle: number, value: any): Thenable<boolean>;
|
||||
export type WebviewHandle = number;
|
||||
|
||||
export interface MainThreadWebviewsShape extends IDisposable {
|
||||
$createWebview(handle: WebviewHandle, uri: URI, options: vscode.WebviewOptions): void;
|
||||
$disposeWebview(handle: WebviewHandle): void;
|
||||
$show(handle: WebviewHandle, column: EditorPosition): void;
|
||||
$setTitle(handle: WebviewHandle, value: string): void;
|
||||
$setHtml(handle: WebviewHandle, value: string): void;
|
||||
$sendMessage(handle: WebviewHandle, value: any): Thenable<boolean>;
|
||||
}
|
||||
export interface ExtHostWebviewsShape {
|
||||
$onMessage(handle: number, message: any): void;
|
||||
$onBecameActive(handle: number): void;
|
||||
$onBecameInactive(handle: number): void;
|
||||
$onMessage(handle: WebviewHandle, message: any): void;
|
||||
$onDidChangeActiveWeview(handle: WebviewHandle | undefined): void;
|
||||
$onDidDisposeWeview(handle: WebviewHandle): void;
|
||||
$onDidChangePosition(handle: WebviewHandle, newPosition: EditorPosition): void;
|
||||
}
|
||||
|
||||
export interface MainThreadWorkspaceShape extends IDisposable {
|
||||
@@ -817,7 +819,7 @@ export const MainContext = {
|
||||
MainThreadStorage: createMainId<MainThreadStorageShape>('MainThreadStorage'),
|
||||
MainThreadTelemetry: createMainId<MainThreadTelemetryShape>('MainThreadTelemetry'),
|
||||
MainThreadTerminalService: createMainId<MainThreadTerminalServiceShape>('MainThreadTerminalService'),
|
||||
MainThreadWebview: createMainId<MainThreadWebviewShape>('MainThreadWebview'),
|
||||
MainThreadWebviews: createMainId<MainThreadWebviewsShape>('MainThreadWebviews'),
|
||||
MainThreadWorkspace: createMainId<MainThreadWorkspaceShape>('MainThreadWorkspace'),
|
||||
MainThreadFileSystem: createMainId<MainThreadFileSystemShape>('MainThreadFileSystem'),
|
||||
MainThreadExtensionService: createMainId<MainThreadExtensionServiceShape>('MainThreadExtensionService'),
|
||||
|
||||
@@ -12,10 +12,16 @@ import { ExtHostTextEditor } from './extHostTextEditor';
|
||||
import * as assert from 'assert';
|
||||
import * as typeConverters from './extHostTypeConverters';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { ExtHostWebview, ExtHostWebviews } from './extHostWebview';
|
||||
import { Disposable } from './extHostTypes';
|
||||
|
||||
export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsShape {
|
||||
|
||||
private _disposables: Disposable[] = [];
|
||||
|
||||
private _activeEditorId: string;
|
||||
private _activeWebview: ExtHostWebview;
|
||||
|
||||
private readonly _editors = new Map<string, ExtHostTextEditor>();
|
||||
private readonly _documents = new Map<string, ExtHostDocumentData>();
|
||||
|
||||
@@ -23,15 +29,34 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha
|
||||
private readonly _onDidRemoveDocuments = new Emitter<ExtHostDocumentData[]>();
|
||||
private readonly _onDidChangeVisibleTextEditors = new Emitter<ExtHostTextEditor[]>();
|
||||
private readonly _onDidChangeActiveTextEditor = new Emitter<ExtHostTextEditor>();
|
||||
private readonly _onDidChangeActiveEditor = new Emitter<ExtHostTextEditor | ExtHostWebview>();
|
||||
|
||||
readonly onDidAddDocuments: Event<ExtHostDocumentData[]> = this._onDidAddDocuments.event;
|
||||
readonly onDidRemoveDocuments: Event<ExtHostDocumentData[]> = this._onDidRemoveDocuments.event;
|
||||
readonly onDidChangeVisibleTextEditors: Event<ExtHostTextEditor[]> = this._onDidChangeVisibleTextEditors.event;
|
||||
readonly onDidChangeActiveTextEditor: Event<ExtHostTextEditor> = this._onDidChangeActiveTextEditor.event;
|
||||
readonly onDidChangeActiveEditor: Event<ExtHostTextEditor | ExtHostWebview> = this._onDidChangeActiveEditor.event;
|
||||
|
||||
constructor(
|
||||
private readonly _mainContext: IMainContext
|
||||
private readonly _mainContext: IMainContext,
|
||||
_extHostWebviews?: ExtHostWebviews
|
||||
) {
|
||||
if (_extHostWebviews) {
|
||||
_extHostWebviews.onDidChangeActiveWebview(webview => {
|
||||
if (webview) {
|
||||
if (webview !== this._activeWebview) {
|
||||
this._onDidChangeActiveEditor.fire(webview);
|
||||
this._activeWebview = webview;
|
||||
}
|
||||
} else {
|
||||
this._activeWebview = webview;
|
||||
}
|
||||
}, this, this._disposables);
|
||||
}
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._disposables = dispose(this._disposables);
|
||||
}
|
||||
|
||||
$acceptDocumentsAndEditorsDelta(delta: IDocumentsAndEditorsDelta): void {
|
||||
@@ -117,6 +142,9 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha
|
||||
}
|
||||
if (delta.newActiveEditor !== undefined) {
|
||||
this._onDidChangeActiveTextEditor.fire(this.activeEditor());
|
||||
|
||||
const activeEditor = this.activeEditor();
|
||||
this._onDidChangeActiveEditor.fire(activeEditor || this._activeWebview);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -313,7 +313,7 @@ export class ExtHostTextEditorOptions implements vscode.TextEditorOptions {
|
||||
|
||||
export class ExtHostTextEditor implements vscode.TextEditor {
|
||||
|
||||
public readonly type = 'texteditor';
|
||||
public readonly editorType = 'texteditor';
|
||||
|
||||
private readonly _proxy: MainThreadTextEditorsShape;
|
||||
private readonly _id: string;
|
||||
|
||||
@@ -3,49 +3,66 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { MainContext, MainThreadWebviewShape, IMainContext, ExtHostWebviewsShape } from './extHost.protocol';
|
||||
import { MainContext, MainThreadWebviewsShape, IMainContext, ExtHostWebviewsShape, WebviewHandle } from './extHost.protocol';
|
||||
import * as vscode from 'vscode';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import * as typeConverters from 'vs/workbench/api/node/extHostTypeConverters';
|
||||
import { Position } from 'vs/platform/editor/common/editor';
|
||||
|
||||
export class ExtHostWebview implements vscode.Webview {
|
||||
public readonly editorType = 'webview';
|
||||
|
||||
class ExtHostWebview implements vscode.Webview {
|
||||
private _title: string;
|
||||
private _html: string;
|
||||
private _options: vscode.WebviewOptions;
|
||||
private _isDisposed: boolean = false;
|
||||
private _viewColumn: vscode.ViewColumn;
|
||||
|
||||
|
||||
public readonly onMessageEmitter = new Emitter<any>();
|
||||
public readonly onMessage = this.onMessageEmitter.event;
|
||||
public readonly onMessage: Event<any> = this.onMessageEmitter.event;
|
||||
|
||||
public readonly onBecameActiveEmitter = new Emitter<void>();
|
||||
public readonly onBecameActive = this.onBecameActiveEmitter.event;
|
||||
public readonly onDisposeEmitter = new Emitter<void>();
|
||||
public readonly onDispose: Event<void> = this.onDisposeEmitter.event;
|
||||
|
||||
public readonly onBecameInactiveEmitter = new Emitter<void>();
|
||||
public readonly onBecameInactive = this.onBecameInactiveEmitter.event;
|
||||
public readonly onDidChangeViewColumnEmitter = new Emitter<vscode.ViewColumn>();
|
||||
public readonly onDidChangeViewColumn: Event<vscode.ViewColumn> = this.onDidChangeViewColumnEmitter.event;
|
||||
|
||||
constructor(
|
||||
private readonly _proxy: MainThreadWebviewShape,
|
||||
private readonly _handle: number,
|
||||
viewColumn: vscode.ViewColumn
|
||||
private readonly _handle: WebviewHandle,
|
||||
private readonly _proxy: MainThreadWebviewsShape,
|
||||
private readonly _uri: vscode.Uri,
|
||||
viewColumn: vscode.ViewColumn,
|
||||
options: vscode.WebviewOptions
|
||||
) {
|
||||
this._viewColumn = viewColumn;
|
||||
this._options = options;
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
if (this._isDisposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._isDisposed = true;
|
||||
this._proxy.$disposeWebview(this._handle);
|
||||
|
||||
this.onDisposeEmitter.dispose();
|
||||
this.onMessageEmitter.dispose();
|
||||
this.onDidChangeViewColumnEmitter.dispose();
|
||||
}
|
||||
|
||||
get uri(): vscode.Uri {
|
||||
this.assertNotDisposed();
|
||||
return this._uri;
|
||||
}
|
||||
|
||||
get title(): string {
|
||||
this.assertNotDisposed();
|
||||
return this._title;
|
||||
}
|
||||
|
||||
set title(value: string) {
|
||||
this.assertNotDisposed();
|
||||
if (this._title !== value) {
|
||||
this._title = value;
|
||||
this._proxy.$setTitle(this._handle, value);
|
||||
@@ -53,10 +70,12 @@ class ExtHostWebview implements vscode.Webview {
|
||||
}
|
||||
|
||||
get html(): string {
|
||||
this.assertNotDisposed();
|
||||
return this._html;
|
||||
}
|
||||
|
||||
set html(value: string) {
|
||||
this.assertNotDisposed();
|
||||
if (this._html !== value) {
|
||||
this._html = value;
|
||||
this._proxy.$setHtml(this._handle, value);
|
||||
@@ -64,63 +83,88 @@ class ExtHostWebview implements vscode.Webview {
|
||||
}
|
||||
|
||||
get options(): vscode.WebviewOptions {
|
||||
this.assertNotDisposed();
|
||||
return this._options;
|
||||
}
|
||||
|
||||
set options(value: vscode.WebviewOptions) {
|
||||
this._proxy.$setOptions(this._handle, value);
|
||||
get viewColumn(): vscode.ViewColumn {
|
||||
this.assertNotDisposed();
|
||||
return this._viewColumn;
|
||||
}
|
||||
|
||||
get viewColumn(): vscode.ViewColumn {
|
||||
return this._viewColumn;
|
||||
set viewColumn(value: vscode.ViewColumn) {
|
||||
this.assertNotDisposed();
|
||||
this._viewColumn = value;
|
||||
}
|
||||
|
||||
public postMessage(message: any): Thenable<any> {
|
||||
return this._proxy.$sendMessage(this._handle, message);
|
||||
}
|
||||
|
||||
private assertNotDisposed() {
|
||||
if (this._isDisposed) {
|
||||
throw new Error('Webview is disposed');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtHostWebviews implements ExtHostWebviewsShape {
|
||||
private static _handlePool = 0;
|
||||
private static handlePool = 0;
|
||||
|
||||
private readonly _proxy: MainThreadWebviewShape;
|
||||
private readonly _proxy: MainThreadWebviewsShape;
|
||||
|
||||
private readonly _webviews = new Map<number, ExtHostWebview>();
|
||||
private readonly _webviews = new Map<WebviewHandle, ExtHostWebview>();
|
||||
|
||||
constructor(
|
||||
mainContext: IMainContext
|
||||
) {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadWebview);
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadWebviews);
|
||||
}
|
||||
|
||||
createWebview(
|
||||
title: string,
|
||||
uri: vscode.Uri,
|
||||
viewColumn: vscode.ViewColumn,
|
||||
options: vscode.WebviewOptions
|
||||
): vscode.Webview {
|
||||
const handle = ExtHostWebviews._handlePool++;
|
||||
this._proxy.$createWebview(handle);
|
||||
const handle = ExtHostWebviews.handlePool++;
|
||||
if (!this._webviews.has(handle)) {
|
||||
this._proxy.$createWebview(handle, uri, options);
|
||||
|
||||
const webview = new ExtHostWebview(handle, this._proxy, uri, viewColumn, options);
|
||||
this._webviews.set(handle, webview);
|
||||
}
|
||||
|
||||
const webview = new ExtHostWebview(this._proxy, handle, viewColumn);
|
||||
this._webviews.set(handle, webview);
|
||||
webview.title = title;
|
||||
webview.options = options;
|
||||
this._proxy.$show(handle, typeConverters.fromViewColumn(viewColumn));
|
||||
return webview;
|
||||
return this._webviews.get(handle);
|
||||
}
|
||||
|
||||
$onMessage(handle: number, message: any): void {
|
||||
$onMessage(handle: WebviewHandle, message: any): void {
|
||||
const webview = this._webviews.get(handle);
|
||||
|
||||
webview.onMessageEmitter.fire(message);
|
||||
}
|
||||
|
||||
$onBecameActive(handle: number): void {
|
||||
$onDidChangeActiveWeview(handle: WebviewHandle | undefined): void {
|
||||
const webview = this._webviews.get(handle);
|
||||
webview.onBecameActiveEmitter.fire();
|
||||
this._onDidChangeActiveWebview.fire(webview);
|
||||
}
|
||||
|
||||
$onBecameInactive(handle: number): void {
|
||||
$onDidDisposeWeview(handle: WebviewHandle): void {
|
||||
const webview = this._webviews.get(handle);
|
||||
webview.onBecameInactiveEmitter.fire();
|
||||
if (webview) {
|
||||
webview.onDisposeEmitter.fire();
|
||||
}
|
||||
}
|
||||
|
||||
$onDidChangePosition(handle: WebviewHandle, newPosition: Position): void {
|
||||
const webview = this._webviews.get(handle);
|
||||
if (webview) {
|
||||
const newViewColumn = typeConverters.toViewColumn(newPosition);
|
||||
webview.viewColumn = newViewColumn;
|
||||
webview.onDidChangeViewColumnEmitter.fire(newViewColumn);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly _onDidChangeActiveWebview = new Emitter<ExtHostWebview | undefined>();
|
||||
public readonly onDidChangeActiveWebview = this._onDidChangeActiveWebview.event;
|
||||
}
|
||||
Reference in New Issue
Block a user