/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { MainContext, MainThreadWebviewsShape, IMainContext, ExtHostWebviewsShape, WebviewPanelHandle } from './extHost.protocol'; import * as vscode from 'vscode'; 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'; import { TPromise } from 'vs/base/common/winjs.base'; import { Disposable } from './extHostTypes'; export class ExtHostWebview implements vscode.Webview { private readonly _handle: WebviewPanelHandle; private readonly _proxy: MainThreadWebviewsShape; private _html: string; private _options: vscode.WebviewOptions; private _isDisposed: boolean = false; readonly _onMessageEmitter = new Emitter(); public readonly onDidReceiveMessage: Event = this._onMessageEmitter.event; constructor( handle: WebviewPanelHandle, proxy: MainThreadWebviewsShape, options: vscode.WebviewOptions ) { this._handle = handle; this._proxy = proxy; this._options = options; } dispose() { this._onMessageEmitter.dispose(); } 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); } } get options(): vscode.WebviewOptions { this.assertNotDisposed(); return this._options; } public postMessage(message: any): Thenable { this.assertNotDisposed(); return this._proxy.$postMessage(this._handle, message); } private assertNotDisposed() { if (this._isDisposed) { throw new Error('Webview is disposed'); } } } export class ExtHostWebviewPanel implements vscode.WebviewPanel { private readonly _handle: WebviewPanelHandle; private readonly _proxy: MainThreadWebviewsShape; private readonly _viewType: string; private _title: string; private readonly _options: vscode.WebviewPanelOptions; private readonly _webview: ExtHostWebview; private _isDisposed: boolean = false; private _viewColumn: vscode.ViewColumn; private _visible: boolean = true; readonly _onDisposeEmitter = new Emitter(); public readonly onDidDispose: Event = this._onDisposeEmitter.event; readonly _onDidChangeViewStateEmitter = new Emitter(); public readonly onDidChangeViewState: Event = this._onDidChangeViewStateEmitter.event; constructor( handle: WebviewPanelHandle, proxy: MainThreadWebviewsShape, viewType: string, title: string, viewColumn: vscode.ViewColumn, editorOptions: vscode.WebviewPanelOptions, webview: ExtHostWebview ) { this._handle = handle; this._proxy = proxy; this._viewType = viewType; this._options = editorOptions; this._viewColumn = viewColumn; this._title = title; this._webview = webview; } public dispose() { if (this._isDisposed) { return; } this._isDisposed = true; this._onDisposeEmitter.fire(); this._proxy.$disposeWebview(this._handle); this._webview.dispose(); this._onDisposeEmitter.dispose(); this._onDidChangeViewStateEmitter.dispose(); } get webview() { this.assertNotDisposed(); return this._webview; } get viewType(): string { this.assertNotDisposed(); return this._viewType; } 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); } } get options() { return this._options; } get viewColumn(): vscode.ViewColumn { this.assertNotDisposed(); return this._viewColumn; } _setViewColumn(value: vscode.ViewColumn) { this.assertNotDisposed(); this._viewColumn = value; } get visible(): boolean { this.assertNotDisposed(); return this._visible; } _setVisible(value: boolean) { this.assertNotDisposed(); this._visible = value; } public postMessage(message: any): Thenable { this.assertNotDisposed(); return this._proxy.$postMessage(this._handle, message); } public reveal(viewColumn?: vscode.ViewColumn): void { this.assertNotDisposed(); this._proxy.$reveal(this._handle, viewColumn ? typeConverters.fromViewColumn(viewColumn) : undefined); } private assertNotDisposed() { if (this._isDisposed) { throw new Error('Webview is disposed'); } } } export class ExtHostWebviews implements ExtHostWebviewsShape { private static webviewHandlePool = 1; private readonly _proxy: MainThreadWebviewsShape; private readonly _webviewPanels = new Map(); private readonly _serializers = new Map(); constructor( mainContext: IMainContext ) { this._proxy = mainContext.getProxy(MainContext.MainThreadWebviews); } createWebview( viewType: string, title: string, viewColumn: vscode.ViewColumn, options: (vscode.WebviewPanelOptions & vscode.WebviewOptions) | undefined, extensionFolderPath: string ): vscode.WebviewPanel { options = options || {}; const handle = ExtHostWebviews.webviewHandlePool++ + ''; this._proxy.$createWebviewPanel(handle, viewType, title, typeConverters.fromViewColumn(viewColumn), options, extensionFolderPath); const webview = new ExtHostWebview(handle, this._proxy, options); const panel = new ExtHostWebviewPanel(handle, this._proxy, viewType, title, viewColumn, options, webview); this._webviewPanels.set(handle, panel); return panel; } registerWebviewPanelSerializer( viewType: string, serializer: vscode.WebviewPanelSerializer ): vscode.Disposable { if (this._serializers.has(viewType)) { throw new Error(`Serializer for '${viewType}' already registered`); } this._serializers.set(viewType, serializer); this._proxy.$registerSerializer(viewType); return new Disposable(() => { this._serializers.delete(viewType); this._proxy.$unregisterSerializer(viewType); }); } $onMessage(handle: WebviewPanelHandle, message: any): void { const panel = this.getWebviewPanel(handle); if (panel) { panel.webview._onMessageEmitter.fire(message); } } $onDidChangeWebviewPanelViewState(handle: WebviewPanelHandle, visible: boolean, position: Position): void { const panel = this.getWebviewPanel(handle); if (panel) { const viewColumn = typeConverters.toViewColumn(position); if (panel.visible !== visible || panel.viewColumn !== viewColumn) { panel._setVisible(visible); panel._setViewColumn(viewColumn); panel._onDidChangeViewStateEmitter.fire({ webviewPanel: panel }); } } } $onDidDisposeWebviewPanel(handle: WebviewPanelHandle): Thenable { const panel = this.getWebviewPanel(handle); if (panel) { panel.dispose(); this._webviewPanels.delete(handle); } return TPromise.as(void 0); } $deserializeWebviewPanel( webviewHandle: WebviewPanelHandle, viewType: string, title: string, state: any, position: Position, options: vscode.WebviewOptions & vscode.WebviewPanelOptions ): Thenable { const serializer = this._serializers.get(viewType); if (!serializer) { return TPromise.wrapError(new Error(`No serializer found for '${viewType}'`)); } const webview = new ExtHostWebview(webviewHandle, this._proxy, options); const revivedPanel = new ExtHostWebviewPanel(webviewHandle, this._proxy, viewType, title, typeConverters.toViewColumn(position), options, webview); this._webviewPanels.set(webviewHandle, revivedPanel); return serializer.deserializeWebviewPanel(revivedPanel, state); } $serializeWebviewPanel( webviewHandle: WebviewPanelHandle ): Thenable { const panel = this.getWebviewPanel(webviewHandle); if (!panel) { return TPromise.as(undefined); } const serialzer = this._serializers.get(panel.viewType); if (!serialzer) { return TPromise.as(undefined); } return serialzer.serializeWebviewPanel(panel); } private getWebviewPanel(handle: WebviewPanelHandle): ExtHostWebviewPanel | undefined { return this._webviewPanels.get(handle); } }