未验证 提交 2038b8fc 编写于 作者: M Matt Bierner 提交者: GitHub

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
上级 de87042d
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
return; return;
} }
didShow = true; didShow = true;
const args = [settings.previewUri];
const notification = document.createElement('a'); const notification = document.createElement('a');
notification.innerText = strings.cspAlertMessageText; notification.innerText = strings.cspAlertMessageText;
...@@ -25,8 +24,12 @@ ...@@ -25,8 +24,12 @@
notification.setAttribute('role', 'button'); notification.setAttribute('role', 'button');
notification.setAttribute('aria-label', strings.cspAlertMessageLabel); notification.setAttribute('aria-label', strings.cspAlertMessageLabel);
notification.setAttribute('href', `command:markdown.showPreviewSecuritySelector?${encodeURIComponent(JSON.stringify(args))}`); notification.onclick = () => {
window.parent.postMessage({
command: 'markdown.showPreviewSecuritySelector',
args: [settings.source]
}, '*');
};
document.body.appendChild(notification); document.body.appendChild(notification);
}; };
......
...@@ -146,7 +146,7 @@ ...@@ -146,7 +146,7 @@
} else { } else {
scrollTo = previous.element.getBoundingClientRect().top; scrollTo = previous.element.getBoundingClientRect().top;
} }
window.scroll(0, window.scrollY + scrollTo + getSourceRevealAddedOffset()); window.scroll(0, Math.max(1, window.scrollY + scrollTo + getSourceRevealAddedOffset()));
} }
} }
...@@ -193,13 +193,13 @@ ...@@ -193,13 +193,13 @@
function onLoad() { function onLoad() {
if (settings.scrollPreviewWithEditorSelection) { if (settings.scrollPreviewWithEditorSelection) {
const initialLine = +settings.line; setTimeout(() => {
if (!isNaN(initialLine)) { const initialLine = +settings.line;
setTimeout(() => { if (!isNaN(initialLine)) {
scrollDisabled = true; scrollDisabled = true;
scrollToRevealSourceLine(initialLine); scrollToRevealSourceLine(initialLine);
}, 0); }
} }, 0);
} }
} }
...@@ -220,8 +220,13 @@ ...@@ -220,8 +220,13 @@
scrollToRevealSourceLine(line); scrollToRevealSourceLine(line);
}, 50); }, 50);
return event => { return event => {
if (event.data.source !== settings.source) {
return;
}
const line = +event.data.line; const line = +event.data.line;
if (!isNaN(line)) { if (!isNaN(line)) {
settings.line = line;
doScroll(line); doScroll(line);
} }
}; };
......
...@@ -3,25 +3,17 @@ ...@@ -3,25 +3,17 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { Command } from '../commandManager'; import { Command } from '../commandManager';
import { isMarkdownFile, getMarkdownUri, MarkdownPreviewWebviewManager } from '../features/previewContentProvider'; import { MarkdownPreviewManager } from '../features/previewContentProvider';
export class RefreshPreviewCommand implements Command { export class RefreshPreviewCommand implements Command {
public readonly id = 'markdown.refreshPreview'; public readonly id = 'markdown.refreshPreview';
public constructor( public constructor(
private readonly webviewManager: MarkdownPreviewWebviewManager private readonly webviewManager: MarkdownPreviewManager
) { } ) { }
public execute(resource: string | undefined) { public execute() {
if (resource) { this.webviewManager.refresh();
const source = vscode.Uri.parse(resource);
this.webviewManager.update(source);
} else if (vscode.window.activeTextEditor && isMarkdownFile(vscode.window.activeTextEditor.document)) {
this.webviewManager.update(getMarkdownUri(vscode.window.activeTextEditor.document.uri));
} else {
this.webviewManager.updateAll();
}
} }
} }
\ No newline at end of file
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { Command } from '../commandManager'; import { Command } from '../commandManager';
import { MarkdownPreviewWebviewManager, } from '../features/previewContentProvider'; import { MarkdownPreviewManager, } from '../features/previewContentProvider';
import { TelemetryReporter } from '../telemetryReporter'; import { TelemetryReporter } from '../telemetryReporter';
...@@ -30,12 +30,12 @@ function getViewColumn(sideBySide: boolean): vscode.ViewColumn | undefined { ...@@ -30,12 +30,12 @@ function getViewColumn(sideBySide: boolean): vscode.ViewColumn | undefined {
return active.viewColumn; return active.viewColumn;
} }
function showPreview( async function showPreview(
webviewManager: MarkdownPreviewWebviewManager, webviewManager: MarkdownPreviewManager,
telemetryReporter: TelemetryReporter, telemetryReporter: TelemetryReporter,
uri?: vscode.Uri, uri?: vscode.Uri,
sideBySide: boolean = false, sideBySide: boolean = false,
) { ): Promise<any> {
let resource = uri; let resource = uri;
if (!(resource instanceof vscode.Uri)) { if (!(resource instanceof vscode.Uri)) {
if (vscode.window.activeTextEditor) { if (vscode.window.activeTextEditor) {
...@@ -53,23 +53,22 @@ function showPreview( ...@@ -53,23 +53,22 @@ function showPreview(
return; return;
} }
const view = webviewManager.create( webviewManager.preview(
resource, resource,
(vscode.window.activeTextEditor && vscode.window.activeTextEditor.viewColumn) || vscode.ViewColumn.One,
getViewColumn(sideBySide) || vscode.ViewColumn.Active); getViewColumn(sideBySide) || vscode.ViewColumn.Active);
telemetryReporter.sendTelemetryEvent('openPreview', { telemetryReporter.sendTelemetryEvent('openPreview', {
where: sideBySide ? 'sideBySide' : 'inPlace', where: sideBySide ? 'sideBySide' : 'inPlace',
how: (uri instanceof vscode.Uri) ? 'action' : 'pallete' how: (uri instanceof vscode.Uri) ? 'action' : 'pallete'
}); });
return view;
} }
export class ShowPreviewCommand implements Command { export class ShowPreviewCommand implements Command {
public readonly id = 'markdown.showPreview'; public readonly id = 'markdown.showPreview';
public constructor( public constructor(
private readonly webviewManager: MarkdownPreviewWebviewManager, private readonly webviewManager: MarkdownPreviewManager,
private readonly telemetryReporter: TelemetryReporter private readonly telemetryReporter: TelemetryReporter
) { } ) { }
...@@ -84,7 +83,7 @@ export class ShowPreviewToSideCommand implements Command { ...@@ -84,7 +83,7 @@ export class ShowPreviewToSideCommand implements Command {
public readonly id = 'markdown.showPreviewToSide'; public readonly id = 'markdown.showPreviewToSide';
public constructor( public constructor(
private readonly webviewManager: MarkdownPreviewWebviewManager, private readonly webviewManager: MarkdownPreviewManager,
private readonly telemetryReporter: TelemetryReporter private readonly telemetryReporter: TelemetryReporter
) { } ) { }
......
...@@ -14,7 +14,7 @@ import { loadDefaultTelemetryReporter } from './telemetryReporter'; ...@@ -14,7 +14,7 @@ import { loadDefaultTelemetryReporter } from './telemetryReporter';
import { loadMarkdownExtensions } from './markdownExtensions'; import { loadMarkdownExtensions } from './markdownExtensions';
import LinkProvider from './features/documentLinkProvider'; import LinkProvider from './features/documentLinkProvider';
import MDDocumentSymbolProvider from './features/documentSymbolProvider'; import MDDocumentSymbolProvider from './features/documentSymbolProvider';
import { MarkdownContentProvider, getMarkdownUri, isMarkdownFile, MarkdownPreviewWebviewManager } from './features/previewContentProvider'; import { MarkdownContentProvider, MarkdownPreviewManager } from './features/previewContentProvider';
export function activate(context: vscode.ExtensionContext) { export function activate(context: vscode.ExtensionContext) {
...@@ -30,20 +30,20 @@ export function activate(context: vscode.ExtensionContext) { ...@@ -30,20 +30,20 @@ export function activate(context: vscode.ExtensionContext) {
const contentProvider = new MarkdownContentProvider(engine, context, cspArbiter, logger); const contentProvider = new MarkdownContentProvider(engine, context, cspArbiter, logger);
loadMarkdownExtensions(contentProvider, engine); loadMarkdownExtensions(contentProvider, engine);
const webviewManager = new MarkdownPreviewWebviewManager(contentProvider); const previewManager = new MarkdownPreviewManager(contentProvider, logger);
context.subscriptions.push(webviewManager); context.subscriptions.push(previewManager);
context.subscriptions.push(vscode.languages.registerDocumentSymbolProvider(selector, new MDDocumentSymbolProvider(engine))); context.subscriptions.push(vscode.languages.registerDocumentSymbolProvider(selector, new MDDocumentSymbolProvider(engine)));
context.subscriptions.push(vscode.languages.registerDocumentLinkProvider(selector, new LinkProvider())); context.subscriptions.push(vscode.languages.registerDocumentLinkProvider(selector, new LinkProvider()));
const previewSecuritySelector = new PreviewSecuritySelector(cspArbiter, webviewManager); const previewSecuritySelector = new PreviewSecuritySelector(cspArbiter, previewManager);
const commandManager = new CommandManager(); const commandManager = new CommandManager();
context.subscriptions.push(commandManager); context.subscriptions.push(commandManager);
commandManager.register(new commands.ShowPreviewCommand(webviewManager, telemetryReporter)); commandManager.register(new commands.ShowPreviewCommand(previewManager, telemetryReporter));
commandManager.register(new commands.ShowPreviewToSideCommand(webviewManager, telemetryReporter)); commandManager.register(new commands.ShowPreviewToSideCommand(previewManager, telemetryReporter));
commandManager.register(new commands.ShowSourceCommand()); commandManager.register(new commands.ShowSourceCommand());
commandManager.register(new commands.RefreshPreviewCommand(webviewManager)); commandManager.register(new commands.RefreshPreviewCommand(previewManager));
commandManager.register(new commands.RevealLineCommand(logger)); commandManager.register(new commands.RevealLineCommand(logger));
commandManager.register(new commands.MoveCursorToPositionCommand()); commandManager.register(new commands.MoveCursorToPositionCommand());
commandManager.register(new commands.ShowPreviewSecuritySelectorCommand(previewSecuritySelector)); commandManager.register(new commands.ShowPreviewSecuritySelectorCommand(previewSecuritySelector));
...@@ -53,19 +53,6 @@ export function activate(context: vscode.ExtensionContext) { ...@@ -53,19 +53,6 @@ export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(() => { context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(() => {
logger.updateConfiguration(); logger.updateConfiguration();
webviewManager.updateConfiguration(); previewManager.updateConfiguration();
}));
context.subscriptions.push(vscode.window.onDidChangeTextEditorSelection(event => {
if (isMarkdownFile(event.textEditor.document)) {
const markdownFile = getMarkdownUri(event.textEditor.document.uri);
logger.log('updatePreviewForSelection', { markdownFile: markdownFile.toString() });
vscode.commands.executeCommand('_workbench.htmlPreview.postMessage',
markdownFile,
{
line: event.selections[0].active.line
});
}
})); }));
} }
...@@ -19,20 +19,7 @@ const previewStrings = { ...@@ -19,20 +19,7 @@ const previewStrings = {
}; };
export function isMarkdownFile(document: vscode.TextDocument) { export function isMarkdownFile(document: vscode.TextDocument) {
return document.languageId === 'markdown' return document.languageId === 'markdown';
&& document.uri.scheme !== MarkdownContentProvider.scheme; // prevent processing of own documents
}
export function getMarkdownUri(uri: vscode.Uri) {
if (uri.scheme === MarkdownContentProvider.scheme) {
return uri;
}
return uri.with({
scheme: MarkdownContentProvider.scheme,
path: uri.path + '.rendered',
query: uri.toString()
});
} }
export class MarkdownPreviewConfig { export class MarkdownPreviewConfig {
...@@ -137,8 +124,6 @@ export class PreviewConfigManager { ...@@ -137,8 +124,6 @@ export class PreviewConfigManager {
} }
export class MarkdownContentProvider { export class MarkdownContentProvider {
public static readonly scheme = 'markdown';
private extraStyles: Array<vscode.Uri> = []; private extraStyles: Array<vscode.Uri> = [];
private extraScripts: Array<vscode.Uri> = []; private extraScripts: Array<vscode.Uri> = [];
...@@ -234,19 +219,13 @@ export class MarkdownContentProvider { ...@@ -234,19 +219,13 @@ export class MarkdownContentProvider {
public async provideTextDocumentContent( public async provideTextDocumentContent(
sourceUri: vscode.Uri, sourceUri: vscode.Uri,
previewConfigurations: PreviewConfigManager previewConfigurations: PreviewConfigManager,
initialLine: number | undefined = undefined
): Promise<string> { ): Promise<string> {
let initialLine: number | undefined = undefined;
const editor = vscode.window.activeTextEditor;
if (editor && editor.document.uri.toString() === sourceUri.toString()) {
initialLine = editor.selection.active.line;
}
const document = await vscode.workspace.openTextDocument(sourceUri); const document = await vscode.workspace.openTextDocument(sourceUri);
const config = previewConfigurations.loadAndCacheConfiguration(sourceUri); const config = previewConfigurations.loadAndCacheConfiguration(sourceUri);
const initialData = { const initialData = {
previewUri: sourceUri.toString(), // TODO
source: sourceUri.toString(), source: sourceUri.toString(),
line: initialLine, line: initialLine,
scrollPreviewWithEditorSelection: config.scrollPreviewWithEditorSelection, scrollPreviewWithEditorSelection: config.scrollPreviewWithEditorSelection,
...@@ -296,21 +275,99 @@ export class MarkdownContentProvider { ...@@ -296,21 +275,99 @@ export class MarkdownContentProvider {
} }
} }
export class MarkdownPreviewWebviewManager { class MarkdownPreview {
private readonly webviews = new Map<string, vscode.Webview>(); private throttleTimer: any;
private initialLine: number | undefined = undefined;
constructor(
public resource: vscode.Uri,
public webview: vscode.Webview,
public ofColumn: vscode.ViewColumn,
private readonly contentProvider: MarkdownContentProvider,
private readonly previewConfigurations: PreviewConfigManager
) { }
public update(resource: vscode.Uri) {
const editor = vscode.window.activeTextEditor;
if (editor && editor.document.uri.fsPath === resource.fsPath) {
this.initialLine = editor.selection.active.line;
} else {
this.initialLine = undefined;
}
// Schedule update
if (!this.throttleTimer) {
this.throttleTimer = setTimeout(() => this.doUpdate(), resource.fsPath === this.resource.fsPath ? 300 : 0);
}
this.resource = resource;
}
public updateForSelection(resource: vscode.Uri, line: number) {
if (this.resource.fsPath !== resource.fsPath) {
return;
}
this.initialLine = line;
this.webview.postMessage({ line, source: resource.toString() });
}
private getPreviewTitle(resource: vscode.Uri): string {
return localize('previewTitle', 'Preview {0}', path.basename(resource.fsPath));
}
private doUpdate() {
const resource = this.resource;
this.throttleTimer = undefined;
this.contentProvider.provideTextDocumentContent(resource, this.previewConfigurations, this.initialLine)
.then(content => {
if (this.resource === resource) {
this.webview.title = this.getPreviewTitle(this.resource);
this.webview.html = content;
}
});
}
}
export class MarkdownPreviewManager {
private static webviewId = vscode.Uri.parse('vscode-markdown-preview://preview');
private previews: MarkdownPreview[] = [];
private readonly previewConfigurations = new PreviewConfigManager(); private readonly previewConfigurations = new PreviewConfigManager();
private readonly disposables: vscode.Disposable[] = []; private readonly disposables: vscode.Disposable[] = [];
public constructor( public constructor(
private readonly contentProvider: MarkdownContentProvider private readonly contentProvider: MarkdownContentProvider,
private readonly logger: Logger
) { ) {
vscode.workspace.onDidSaveTextDocument(document => { vscode.workspace.onDidChangeTextDocument(event => {
this.update(document.uri); this.update(event.document, undefined);
}, null, this.disposables); }, null, this.disposables);
vscode.workspace.onDidChangeTextDocument(event => { vscode.window.onDidChangeActiveEditor(editor => {
this.update(event.document.uri); vscode.commands.executeCommand('setContext', 'markdownPreview', editor && editor.editorType === 'webview' && editor.uri.fsPath === MarkdownPreviewManager.webviewId.fsPath);
if (editor && editor.editorType === 'texteditor') {
if (isMarkdownFile(editor.document)) {
for (const preview of this.previews.filter(preview => preview.ofColumn === editor.viewColumn)) {
preview.update(editor.document.uri);
}
}
}
}, null, this.disposables);
vscode.window.onDidChangeTextEditorSelection(event => {
if (!isMarkdownFile(event.textEditor.document)) {
return;
}
const resource = event.textEditor.document.uri;
for (const previewForResource of this.previews.filter(preview => preview.resource.fsPath === resource.fsPath)) {
this.logger.log('updatePreviewForSelection', { markdownFile: resource });
previewForResource.updateForSelection(resource, event.selections[0].active.line);
}
}, null, this.disposables); }, null, this.disposables);
} }
...@@ -321,65 +378,73 @@ export class MarkdownPreviewWebviewManager { ...@@ -321,65 +378,73 @@ export class MarkdownPreviewWebviewManager {
item.dispose(); item.dispose();
} }
} }
this.webviews.clear(); this.previews = [];
} }
public update(uri: vscode.Uri) { public refresh() {
const webview = this.webviews.get(uri.fsPath); for (const preview of this.previews) {
if (webview) { preview.update(preview.resource);
this.contentProvider.provideTextDocumentContent(uri, this.previewConfigurations).then(x => webview.html = x);
} }
} }
public updateAll() { public updateConfiguration() {
for (const resource of this.webviews.keys()) { for (const preview of this.previews) {
const sourceUri = vscode.Uri.parse(resource); if (this.previewConfigurations.shouldUpdateConfiguration(preview.resource)) {
this.update(sourceUri); preview.update(preview.resource);
}
} }
} }
public updateConfiguration() { private update(document: vscode.TextDocument, viewColumn: vscode.ViewColumn | undefined) {
for (const resource of this.webviews.keys()) { if (!isMarkdownFile(document)) {
const sourceUri = vscode.Uri.parse(resource); return;
if (this.previewConfigurations.shouldUpdateConfiguration(sourceUri)) { }
this.update(sourceUri);
for (const preview of this.previews) {
if (preview.resource.fsPath === document.uri.fsPath || viewColumn && preview.ofColumn === viewColumn) {
preview.update(document.uri);
} }
} }
} }
public create( public preview(
resource: vscode.Uri, resource: vscode.Uri,
viewColumn: vscode.ViewColumn resourceColumn: vscode.ViewColumn,
previewColumn: vscode.ViewColumn
) { ) {
const view = vscode.window.createWebview( // Only allow a single markdown preview per column
localize('previewTitle', 'Preview {0}', path.basename(resource.fsPath)), let preview = this.previews.find(preview => preview.webview.viewColumn === previewColumn);
viewColumn, if (preview) {
{ preview.resource = resource;
enableScripts: true, preview.ofColumn = resourceColumn;
localResourceRoots: this.getLocalResourceRoots(resource) } else {
const webview = vscode.window.createWebview(
MarkdownPreviewManager.webviewId,
previewColumn, {
enableScripts: true,
localResourceRoots: this.getLocalResourceRoots(resource)
});
webview.onDispose(() => {
const existing = this.previews.findIndex(preview => preview.webview === webview);
if (existing >= 0) {
this.previews.splice(existing, 1);
}
}); });
this.contentProvider.provideTextDocumentContent(resource, this.previewConfigurations).then(x => view.html = x); webview.onMessage(e => {
vscode.commands.executeCommand(e.command, ...e.args);
view.onMessage(e => { });
vscode.commands.executeCommand(e.command, ...e.args);
});
view.onBecameActive(() => {
vscode.commands.executeCommand('setContext', 'markdownPreview', true);
});
view.onBecameInactive(() => { preview = new MarkdownPreview(resource, webview, resourceColumn, this.contentProvider, this.previewConfigurations);
vscode.commands.executeCommand('setContext', 'markdownPreview', false); this.previews.push(preview);
}); }
this.webviews.set(resource.fsPath, view); preview.update(preview.resource);
return view; return preview.webview;
} }
private getLocalResourceRoots( private getLocalResourceRoots(resource: vscode.Uri): vscode.Uri[] {
resource: vscode.Uri
): vscode.Uri[] {
const folder = vscode.workspace.getWorkspaceFolder(resource); const folder = vscode.workspace.getWorkspaceFolder(resource);
if (folder) { if (folder) {
return [folder.uri]; return [folder.uri];
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { getMarkdownUri, MarkdownPreviewWebviewManager } from './features/previewContentProvider'; import { MarkdownPreviewManager } from './features/previewContentProvider';
import * as nls from 'vscode-nls'; import * as nls from 'vscode-nls';
...@@ -90,7 +90,7 @@ export class PreviewSecuritySelector { ...@@ -90,7 +90,7 @@ export class PreviewSecuritySelector {
public constructor( public constructor(
private cspArbiter: ContentSecurityPolicyArbiter, private cspArbiter: ContentSecurityPolicyArbiter,
private webviewManager: MarkdownPreviewWebviewManager private webviewManager: MarkdownPreviewManager
) { } ) { }
public async showSecutitySelectorForResource(resource: vscode.Uri): Promise<void> { public async showSecutitySelectorForResource(resource: vscode.Uri): Promise<void> {
...@@ -143,15 +143,12 @@ export class PreviewSecuritySelector { ...@@ -143,15 +143,12 @@ export class PreviewSecuritySelector {
return; return;
} }
const sourceUri = getMarkdownUri(resource);
if (selection.type === 'toggle') { if (selection.type === 'toggle') {
this.cspArbiter.setShouldDisableSecurityWarning(!this.cspArbiter.shouldDisableSecurityWarnings()); this.cspArbiter.setShouldDisableSecurityWarning(!this.cspArbiter.shouldDisableSecurityWarnings());
this.webviewManager.update(sourceUri);
return; return;
} else {
await this.cspArbiter.setSecurityLevelForResource(resource, selection.type);
} }
this.webviewManager.refresh();
await this.cspArbiter.setSecurityLevelForResource(resource, selection.type);
this.webviewManager.update(sourceUri);
} }
} }
...@@ -513,19 +513,31 @@ declare module 'vscode' { ...@@ -513,19 +513,31 @@ declare module 'vscode' {
*/ */
export interface Webview { export interface Webview {
/** /**
* Title of the webview. * Type identifying the editor as a webview editor.
*/ */
title: string; readonly editorType: 'webview';
/** /**
* Contents of the webview. * Unique identifer of the webview.
*/ */
html: string; readonly uri: Uri;
/** /**
* Content settings for the webview. * Content settings for the webview.
*/ */
options: WebviewOptions; readonly options: WebviewOptions;
/**
* Title of the webview shown in UI.
*/
title: string;
/**
* Contents of the webview.
*
* Should be a complete html document.
*/
html: string;
/** /**
* The column in which the webview is showing. * The column in which the webview is showing.
...@@ -538,14 +550,14 @@ declare module 'vscode' { ...@@ -538,14 +550,14 @@ declare module 'vscode' {
readonly onMessage: Event<any>; readonly onMessage: Event<any>;
/** /**
* Fired when the webview becomes the active editor. * Fired when the webview is disposed.
*/ */
readonly onBecameActive: Event<void>; readonly onDispose: Event<void>;
/** /**
* Fired when the webview stops being the active editor * Fired when the webview's view column changes.
*/ */
readonly onBecameInactive: Event<void>; readonly onDidChangeViewColumn: Event<ViewColumn>;
/** /**
* Post a message to the webview content. * Post a message to the webview content.
...@@ -554,23 +566,39 @@ declare module 'vscode' { ...@@ -554,23 +566,39 @@ declare module 'vscode' {
* *
* @param message Body of the message. * @param message Body of the message.
*/ */
postMessage(message: any): Thenable<any>; postMessage(message: any): Thenable<boolean>;
/** /**
* Dispose the webview. * Dispose of the the webview.
*
* This closes the webview if it showing and disposes of the resources owned by the webview.
* Webview are also disposed when the user closes the webview editor. Both cases fire `onDispose`
* event. Trying to use the webview after it has been disposed throws an exception.
*/ */
dispose(): any; dispose(): any;
} }
export interface TextEditor {
/**
* Type identifying the editor as a text editor.
*/
readonly editorType: 'texteditor';
}
namespace window { namespace window {
/** /**
* Create and show a new webview. * Create and show a new webview.
* *
* @param title Title of the webview. * @param uri Unique identifier for the webview.
* @param column Editor column to show the new webview in. * @param column Editor column to show the new webview in.
* @param options Webview content options. * @param options Content settings for the webview.
*/
export function createWebview(uri: Uri, column: ViewColumn, options: WebviewOptions): Webview;
/**
* Event fired when the active editor changes.
*/ */
export function createWebview(title: string, column: ViewColumn, options: WebviewOptions): Webview; export const onDidChangeActiveEditor: Event<TextEditor | Webview | undefined>;
} }
export namespace window { export namespace window {
......
...@@ -97,7 +97,8 @@ export function createApiFactory( ...@@ -97,7 +97,8 @@ export function createApiFactory(
rpcProtocol.set(ExtHostContext.ExtHostLogService, extHostLogService); rpcProtocol.set(ExtHostContext.ExtHostLogService, extHostLogService);
const extHostHeapService = rpcProtocol.set(ExtHostContext.ExtHostHeapService, new ExtHostHeapService()); const extHostHeapService = rpcProtocol.set(ExtHostContext.ExtHostHeapService, new ExtHostHeapService());
const extHostDecorations = rpcProtocol.set(ExtHostContext.ExtHostDecorations, new ExtHostDecorations(rpcProtocol)); 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 extHostDocuments = rpcProtocol.set(ExtHostContext.ExtHostDocuments, new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors));
const extHostDocumentContentProviders = rpcProtocol.set(ExtHostContext.ExtHostDocumentContentProviders, new ExtHostDocumentContentProvider(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))); const extHostDocumentSaveParticipant = rpcProtocol.set(ExtHostContext.ExtHostDocumentSaveParticipant, new ExtHostDocumentSaveParticipant(extHostLogService, extHostDocuments, rpcProtocol.getProxy(MainContext.MainThreadTextEditors)));
...@@ -117,7 +118,6 @@ export function createApiFactory( ...@@ -117,7 +118,6 @@ export function createApiFactory(
const extHostTask = rpcProtocol.set(ExtHostContext.ExtHostTask, new ExtHostTask(rpcProtocol, extHostWorkspace)); const extHostTask = rpcProtocol.set(ExtHostContext.ExtHostTask, new ExtHostTask(rpcProtocol, extHostWorkspace));
const extHostWindow = rpcProtocol.set(ExtHostContext.ExtHostWindow, new ExtHostWindow(rpcProtocol)); const extHostWindow = rpcProtocol.set(ExtHostContext.ExtHostWindow, new ExtHostWindow(rpcProtocol));
rpcProtocol.set(ExtHostContext.ExtHostExtensionService, extensionService); rpcProtocol.set(ExtHostContext.ExtHostExtensionService, extensionService);
const extHostWebviews = rpcProtocol.set(ExtHostContext.ExtHostWebviews, new ExtHostWebviews(rpcProtocol));
// Check that no named customers are missing // Check that no named customers are missing
const expected: ProxyIdentifier<any>[] = Object.keys(ExtHostContext).map((key) => ExtHostContext[key]); const expected: ProxyIdentifier<any>[] = Object.keys(ExtHostContext).map((key) => ExtHostContext[key]);
...@@ -402,8 +402,11 @@ export function createApiFactory( ...@@ -402,8 +402,11 @@ export function createApiFactory(
registerDecorationProvider: proposedApiFunction(extension, (provider: vscode.DecorationProvider) => { registerDecorationProvider: proposedApiFunction(extension, (provider: vscode.DecorationProvider) => {
return extHostDecorations.registerDecorationProvider(provider, extension.id); return extHostDecorations.registerDecorationProvider(provider, extension.id);
}), }),
createWebview: proposedApiFunction(extension, (name: string, column: vscode.ViewColumn, options: vscode.WebviewOptions) => { createWebview: proposedApiFunction(extension, (uri: vscode.Uri, column: vscode.ViewColumn, options: vscode.WebviewOptions) => {
return extHostWebviews.createWebview(name, column, options); 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 { ...@@ -342,19 +342,21 @@ export interface MainThreadTelemetryShape extends IDisposable {
$publicLog(eventName: string, data?: any): void; $publicLog(eventName: string, data?: any): void;
} }
export interface MainThreadWebviewShape extends IDisposable { export type WebviewHandle = number;
$createWebview(handle: number): void;
$disposeWebview(handle: number): void; export interface MainThreadWebviewsShape extends IDisposable {
$show(handle: number, column: EditorPosition): void; $createWebview(handle: WebviewHandle, uri: URI, options: vscode.WebviewOptions): void;
$setTitle(handle: number, value: string): void; $disposeWebview(handle: WebviewHandle): void;
$setHtml(handle: number, value: string): void; $show(handle: WebviewHandle, column: EditorPosition): void;
$setOptions(handle: number, value: vscode.WebviewOptions): void; $setTitle(handle: WebviewHandle, value: string): void;
$sendMessage(handle: number, value: any): Thenable<boolean>; $setHtml(handle: WebviewHandle, value: string): void;
$sendMessage(handle: WebviewHandle, value: any): Thenable<boolean>;
} }
export interface ExtHostWebviewsShape { export interface ExtHostWebviewsShape {
$onMessage(handle: number, message: any): void; $onMessage(handle: WebviewHandle, message: any): void;
$onBecameActive(handle: number): void; $onDidChangeActiveWeview(handle: WebviewHandle | undefined): void;
$onBecameInactive(handle: number): void; $onDidDisposeWeview(handle: WebviewHandle): void;
$onDidChangePosition(handle: WebviewHandle, newPosition: EditorPosition): void;
} }
export interface MainThreadWorkspaceShape extends IDisposable { export interface MainThreadWorkspaceShape extends IDisposable {
...@@ -817,7 +819,7 @@ export const MainContext = { ...@@ -817,7 +819,7 @@ export const MainContext = {
MainThreadStorage: createMainId<MainThreadStorageShape>('MainThreadStorage'), MainThreadStorage: createMainId<MainThreadStorageShape>('MainThreadStorage'),
MainThreadTelemetry: createMainId<MainThreadTelemetryShape>('MainThreadTelemetry'), MainThreadTelemetry: createMainId<MainThreadTelemetryShape>('MainThreadTelemetry'),
MainThreadTerminalService: createMainId<MainThreadTerminalServiceShape>('MainThreadTerminalService'), MainThreadTerminalService: createMainId<MainThreadTerminalServiceShape>('MainThreadTerminalService'),
MainThreadWebview: createMainId<MainThreadWebviewShape>('MainThreadWebview'), MainThreadWebviews: createMainId<MainThreadWebviewsShape>('MainThreadWebviews'),
MainThreadWorkspace: createMainId<MainThreadWorkspaceShape>('MainThreadWorkspace'), MainThreadWorkspace: createMainId<MainThreadWorkspaceShape>('MainThreadWorkspace'),
MainThreadFileSystem: createMainId<MainThreadFileSystemShape>('MainThreadFileSystem'), MainThreadFileSystem: createMainId<MainThreadFileSystemShape>('MainThreadFileSystem'),
MainThreadExtensionService: createMainId<MainThreadExtensionServiceShape>('MainThreadExtensionService'), MainThreadExtensionService: createMainId<MainThreadExtensionServiceShape>('MainThreadExtensionService'),
......
...@@ -12,10 +12,16 @@ import { ExtHostTextEditor } from './extHostTextEditor'; ...@@ -12,10 +12,16 @@ import { ExtHostTextEditor } from './extHostTextEditor';
import * as assert from 'assert'; import * as assert from 'assert';
import * as typeConverters from './extHostTypeConverters'; import * as typeConverters from './extHostTypeConverters';
import URI from 'vs/base/common/uri'; import URI from 'vs/base/common/uri';
import { ExtHostWebview, ExtHostWebviews } from './extHostWebview';
import { Disposable } from './extHostTypes';
export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsShape { export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsShape {
private _disposables: Disposable[] = [];
private _activeEditorId: string; private _activeEditorId: string;
private _activeWebview: ExtHostWebview;
private readonly _editors = new Map<string, ExtHostTextEditor>(); private readonly _editors = new Map<string, ExtHostTextEditor>();
private readonly _documents = new Map<string, ExtHostDocumentData>(); private readonly _documents = new Map<string, ExtHostDocumentData>();
...@@ -23,15 +29,34 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha ...@@ -23,15 +29,34 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha
private readonly _onDidRemoveDocuments = new Emitter<ExtHostDocumentData[]>(); private readonly _onDidRemoveDocuments = new Emitter<ExtHostDocumentData[]>();
private readonly _onDidChangeVisibleTextEditors = new Emitter<ExtHostTextEditor[]>(); private readonly _onDidChangeVisibleTextEditors = new Emitter<ExtHostTextEditor[]>();
private readonly _onDidChangeActiveTextEditor = new Emitter<ExtHostTextEditor>(); private readonly _onDidChangeActiveTextEditor = new Emitter<ExtHostTextEditor>();
private readonly _onDidChangeActiveEditor = new Emitter<ExtHostTextEditor | ExtHostWebview>();
readonly onDidAddDocuments: Event<ExtHostDocumentData[]> = this._onDidAddDocuments.event; readonly onDidAddDocuments: Event<ExtHostDocumentData[]> = this._onDidAddDocuments.event;
readonly onDidRemoveDocuments: Event<ExtHostDocumentData[]> = this._onDidRemoveDocuments.event; readonly onDidRemoveDocuments: Event<ExtHostDocumentData[]> = this._onDidRemoveDocuments.event;
readonly onDidChangeVisibleTextEditors: Event<ExtHostTextEditor[]> = this._onDidChangeVisibleTextEditors.event; readonly onDidChangeVisibleTextEditors: Event<ExtHostTextEditor[]> = this._onDidChangeVisibleTextEditors.event;
readonly onDidChangeActiveTextEditor: Event<ExtHostTextEditor> = this._onDidChangeActiveTextEditor.event; readonly onDidChangeActiveTextEditor: Event<ExtHostTextEditor> = this._onDidChangeActiveTextEditor.event;
readonly onDidChangeActiveEditor: Event<ExtHostTextEditor | ExtHostWebview> = this._onDidChangeActiveEditor.event;
constructor( 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 { $acceptDocumentsAndEditorsDelta(delta: IDocumentsAndEditorsDelta): void {
...@@ -117,6 +142,9 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha ...@@ -117,6 +142,9 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha
} }
if (delta.newActiveEditor !== undefined) { if (delta.newActiveEditor !== undefined) {
this._onDidChangeActiveTextEditor.fire(this.activeEditor()); 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 { ...@@ -313,7 +313,7 @@ export class ExtHostTextEditorOptions implements vscode.TextEditorOptions {
export class ExtHostTextEditor implements vscode.TextEditor { export class ExtHostTextEditor implements vscode.TextEditor {
public readonly type = 'texteditor'; public readonly editorType = 'texteditor';
private readonly _proxy: MainThreadTextEditorsShape; private readonly _proxy: MainThreadTextEditorsShape;
private readonly _id: string; private readonly _id: string;
......
...@@ -3,49 +3,66 @@ ...@@ -3,49 +3,66 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * 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 * 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 * 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 _title: string;
private _html: string; private _html: string;
private _options: vscode.WebviewOptions; private _options: vscode.WebviewOptions;
private _isDisposed: boolean = false; private _isDisposed: boolean = false;
private _viewColumn: vscode.ViewColumn; private _viewColumn: vscode.ViewColumn;
public readonly onMessageEmitter = new Emitter<any>(); 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 onDisposeEmitter = new Emitter<void>();
public readonly onBecameActive = this.onBecameActiveEmitter.event; public readonly onDispose: Event<void> = this.onDisposeEmitter.event;
public readonly onBecameInactiveEmitter = new Emitter<void>(); public readonly onDidChangeViewColumnEmitter = new Emitter<vscode.ViewColumn>();
public readonly onBecameInactive = this.onBecameInactiveEmitter.event; public readonly onDidChangeViewColumn: Event<vscode.ViewColumn> = this.onDidChangeViewColumnEmitter.event;
constructor( constructor(
private readonly _proxy: MainThreadWebviewShape, private readonly _handle: WebviewHandle,
private readonly _handle: number, private readonly _proxy: MainThreadWebviewsShape,
viewColumn: vscode.ViewColumn private readonly _uri: vscode.Uri,
viewColumn: vscode.ViewColumn,
options: vscode.WebviewOptions
) { ) {
this._viewColumn = viewColumn; this._viewColumn = viewColumn;
this._options = options;
} }
public dispose() { public dispose() {
if (this._isDisposed) { if (this._isDisposed) {
return; return;
} }
this._isDisposed = true; this._isDisposed = true;
this._proxy.$disposeWebview(this._handle); 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 { get title(): string {
this.assertNotDisposed();
return this._title; return this._title;
} }
set title(value: string) { set title(value: string) {
this.assertNotDisposed();
if (this._title !== value) { if (this._title !== value) {
this._title = value; this._title = value;
this._proxy.$setTitle(this._handle, value); this._proxy.$setTitle(this._handle, value);
...@@ -53,10 +70,12 @@ class ExtHostWebview implements vscode.Webview { ...@@ -53,10 +70,12 @@ class ExtHostWebview implements vscode.Webview {
} }
get html(): string { get html(): string {
this.assertNotDisposed();
return this._html; return this._html;
} }
set html(value: string) { set html(value: string) {
this.assertNotDisposed();
if (this._html !== value) { if (this._html !== value) {
this._html = value; this._html = value;
this._proxy.$setHtml(this._handle, value); this._proxy.$setHtml(this._handle, value);
...@@ -64,63 +83,88 @@ class ExtHostWebview implements vscode.Webview { ...@@ -64,63 +83,88 @@ class ExtHostWebview implements vscode.Webview {
} }
get options(): vscode.WebviewOptions { get options(): vscode.WebviewOptions {
this.assertNotDisposed();
return this._options; return this._options;
} }
set options(value: vscode.WebviewOptions) {
this._proxy.$setOptions(this._handle, value);
}
get viewColumn(): vscode.ViewColumn { get viewColumn(): vscode.ViewColumn {
this.assertNotDisposed();
return this._viewColumn; return this._viewColumn;
} }
set viewColumn(value: vscode.ViewColumn) {
this.assertNotDisposed();
this._viewColumn = value;
}
public postMessage(message: any): Thenable<any> { public postMessage(message: any): Thenable<any> {
return this._proxy.$sendMessage(this._handle, message); return this._proxy.$sendMessage(this._handle, message);
} }
private assertNotDisposed() {
if (this._isDisposed) {
throw new Error('Webview is disposed');
}
}
} }
export class ExtHostWebviews implements ExtHostWebviewsShape { 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( constructor(
mainContext: IMainContext mainContext: IMainContext
) { ) {
this._proxy = mainContext.getProxy(MainContext.MainThreadWebview); this._proxy = mainContext.getProxy(MainContext.MainThreadWebviews);
} }
createWebview( createWebview(
title: string, uri: vscode.Uri,
viewColumn: vscode.ViewColumn, viewColumn: vscode.ViewColumn,
options: vscode.WebviewOptions options: vscode.WebviewOptions
): vscode.Webview { ): vscode.Webview {
const handle = ExtHostWebviews._handlePool++; const handle = ExtHostWebviews.handlePool++;
this._proxy.$createWebview(handle); 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)); 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); const webview = this._webviews.get(handle);
webview.onMessageEmitter.fire(message); webview.onMessageEmitter.fire(message);
} }
$onBecameActive(handle: number): void { $onDidChangeActiveWeview(handle: WebviewHandle | undefined): void {
const webview = this._webviews.get(handle); 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); 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;
} }
\ No newline at end of file
...@@ -14,6 +14,8 @@ import { IEditorControl } from 'vs/platform/editor/common/editor'; ...@@ -14,6 +14,8 @@ import { IEditorControl } from 'vs/platform/editor/common/editor';
import Event, { Emitter } from 'vs/base/common/event'; import Event, { Emitter } from 'vs/base/common/event';
import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IConstructorSignature0, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IConstructorSignature0, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import DOM = require('vs/base/browser/dom');
import { IDisposable } from 'vs/base/common/lifecycle';
/** /**
* Composites are layed out in the sidebar and panel part of the workbench. At a time only one composite * Composites are layed out in the sidebar and panel part of the workbench. At a time only one composite
...@@ -27,6 +29,10 @@ import { IConstructorSignature0, IInstantiationService } from 'vs/platform/insta ...@@ -27,6 +29,10 @@ import { IConstructorSignature0, IInstantiationService } from 'vs/platform/insta
*/ */
export abstract class Composite extends Component implements IComposite { export abstract class Composite extends Component implements IComposite {
private _onTitleAreaUpdate: Emitter<void>; private _onTitleAreaUpdate: Emitter<void>;
private _onDidFocus: Emitter<void>;
private _focusTracker?: DOM.IFocusTracker;
private _focusListenerDisposable?: IDisposable;
private visible: boolean; private visible: boolean;
private parent: Builder; private parent: Builder;
...@@ -45,6 +51,7 @@ export abstract class Composite extends Component implements IComposite { ...@@ -45,6 +51,7 @@ export abstract class Composite extends Component implements IComposite {
this.visible = false; this.visible = false;
this._onTitleAreaUpdate = new Emitter<void>(); this._onTitleAreaUpdate = new Emitter<void>();
this._onDidFocus = new Emitter<void>();
} }
public getTitle(): string { public getTitle(): string {
...@@ -85,8 +92,12 @@ export abstract class Composite extends Component implements IComposite { ...@@ -85,8 +92,12 @@ export abstract class Composite extends Component implements IComposite {
return this.parent; return this.parent;
} }
public getFocusContainer(): Builder { public get onDidFocus(): Event<any> {
return this.getContainer(); this._focusTracker = DOM.trackFocus(this.getContainer().getHTMLElement());
this._focusListenerDisposable = this._focusTracker.onDidFocus(() => {
this._onDidFocus.fire();
});
return this._onDidFocus.event;
} }
/** /**
...@@ -189,6 +200,15 @@ export abstract class Composite extends Component implements IComposite { ...@@ -189,6 +200,15 @@ export abstract class Composite extends Component implements IComposite {
public dispose(): void { public dispose(): void {
this._onTitleAreaUpdate.dispose(); this._onTitleAreaUpdate.dispose();
this._onDidFocus.dispose();
if (this._focusTracker) {
this._focusTracker.dispose();
}
if (this._focusListenerDisposable) {
this._focusListenerDisposable.dispose();
}
super.dispose(); super.dispose();
} }
......
...@@ -35,7 +35,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; ...@@ -35,7 +35,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
import { editorBackground, contrastBorder, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { editorBackground, contrastBorder, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry';
import { Themable, EDITOR_GROUP_HEADER_TABS_BACKGROUND, EDITOR_GROUP_HEADER_NO_TABS_BACKGROUND, EDITOR_GROUP_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, EDITOR_GROUP_BACKGROUND, EDITOR_GROUP_HEADER_TABS_BORDER } from 'vs/workbench/common/theme'; import { Themable, EDITOR_GROUP_HEADER_TABS_BACKGROUND, EDITOR_GROUP_HEADER_NO_TABS_BACKGROUND, EDITOR_GROUP_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, EDITOR_GROUP_BACKGROUND, EDITOR_GROUP_HEADER_TABS_BORDER } from 'vs/workbench/common/theme';
import { attachProgressBarStyler } from 'vs/platform/theme/common/styler'; import { attachProgressBarStyler } from 'vs/platform/theme/common/styler';
import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { IDisposable } from 'vs/base/common/lifecycle';
import { ResourcesDropHandler, LocalSelectionTransfer, DraggedEditorIdentifier } from 'vs/workbench/browser/dnd'; import { ResourcesDropHandler, LocalSelectionTransfer, DraggedEditorIdentifier } from 'vs/workbench/browser/dnd';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IPartService } from 'vs/workbench/services/part/common/partService'; import { IPartService } from 'vs/workbench/services/part/common/partService';
...@@ -454,13 +454,9 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro ...@@ -454,13 +454,9 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
this.visibleEditorFocusTrackerDisposable[position].dispose(); this.visibleEditorFocusTrackerDisposable[position].dispose();
} }
// Track focus on editor container this.visibleEditorFocusTrackerDisposable[position] = editor.onDidFocus(() => {
const focusTracker = DOM.trackFocus(editor.getFocusContainer().getHTMLElement());
const listenerDispose = focusTracker.onDidFocus(() => {
this.onFocusGained(editor); this.onFocusGained(editor);
}); });
this.visibleEditorFocusTrackerDisposable[position] = combinedDisposable([focusTracker, listenerDispose]);
} }
private onFocusGained(editor: BaseEditor): void { private onFocusGained(editor: BaseEditor): void {
......
...@@ -70,6 +70,16 @@ ...@@ -70,6 +70,16 @@
} }
} }
function onMessage(message) {
if (enableWrappedPostMessage) {
// Modern webview. Forward wrapped message
ipcRenderer.sendToHost('onmessage', message.data);
} else {
// Old school webview. Forward exact message
ipcRenderer.sendToHost(message.data.command, message.data.data);
}
}
var isHandlingScroll = false; var isHandlingScroll = false;
function handleInnerScroll(event) { function handleInnerScroll(event) {
if (isHandlingScroll) { if (isHandlingScroll) {
...@@ -337,15 +347,7 @@ ...@@ -337,15 +347,7 @@
}); });
// Forward messages from the embedded iframe // Forward messages from the embedded iframe
window.onmessage = function (message) { window.onmessage = onMessage;
if (enableWrappedPostMessage) {
// Modern webview. Forward wrapped message
ipcRenderer.sendToHost('onmessage', message.data);
} else {
// Old school webview. Forward exact message
ipcRenderer.sendToHost(message.data.command, message.data.data);
}
};
// signal ready // signal ready
ipcRenderer.sendToHost('webview-ready', process.pid); ipcRenderer.sendToHost('webview-ready', process.pid);
......
...@@ -234,6 +234,10 @@ export class Webview { ...@@ -234,6 +234,10 @@ export class Webview {
} }
public set contents(value: string) { public set contents(value: string) {
if (this._contents === value) {
return;
}
this._contents = value; this._contents = value;
this._send('content', { this._send('content', {
contents: value, contents: value,
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册