notebooks: pipe renderer API postmessages to the renderer itself (#100414)

* notebooks: pipe renderer API postmessages to the renderer itself

Previously the postMessage on acquireNotebookRenderer API was just
a proxy to the global vscode postmessage. Now, it's linked to the
renderer and will cause an optional `onDidReceiveMessage` method on
the renderer to be called.

The message still _also_ goes to the global webview message handling
for advanced use cases, but this change allows the webview<->renderer
communication to be more nicely contained
and separate for most use cases.

* wip

* fixup! pr comments
This commit is contained in:
Connor Peet
2020-06-22 09:35:16 -07:00
committed by GitHub
parent 5e6729d9bd
commit a57cb45be8
11 changed files with 206 additions and 85 deletions

View File

@@ -494,6 +494,10 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo
cell.detachTextDocument();
}
}
allEditors() {
return this._documentsAndEditors.allEditors();
}
}
export class NotebookEditorCellEditBuilder implements vscode.NotebookEditorCellEdit {
@@ -553,27 +557,48 @@ export class NotebookEditorCellEditBuilder implements vscode.NotebookEditorCellE
}
}
class ExtHostWebviewComm extends Disposable {
onDidReceiveMessage: vscode.Event<any> = this._onDidReceiveMessage.event;
class ExtHostWebviewCommWrapper extends Disposable {
private readonly _onDidReceiveDocumentMessage = new Emitter<any>();
private readonly _rendererIdToEmitters = new Map<string, Emitter<any>>();
constructor(
readonly id: string,
private _editorId: string,
public uri: URI,
private _proxy: MainThreadNotebookShape,
private _onDidReceiveMessage: Emitter<any>,
private _webviewInitData: WebviewInitData,
public document: ExtHostNotebookDocument,
) {
super();
}
async postMessage(message: any): Promise<boolean> {
return this._proxy.$postMessage(this.document.handle, message);
public onDidReceiveMessage(forRendererId: string | undefined, message: any) {
this._onDidReceiveDocumentMessage.fire(message);
if (forRendererId !== undefined) {
this._rendererIdToEmitters.get(forRendererId)?.fire(message);
}
}
asWebviewUri(localResource: vscode.Uri): vscode.Uri {
return asWebviewUri(this._webviewInitData, this.id, localResource);
public readonly contentProviderComm: vscode.NotebookCommunication = {
editorId: this._editorId,
onDidReceiveMessage: this._onDidReceiveDocumentMessage.event,
postMessage: (message: any) => this._proxy.$postMessage(this._editorId, undefined, message),
asWebviewUri: (uri: vscode.Uri) => this._asWebviewUri(uri),
};
public getRendererComm(rendererId: string): vscode.NotebookCommunication {
const emitter = new Emitter<any>();
this._rendererIdToEmitters.set(rendererId, emitter);
return {
editorId: this._editorId,
onDidReceiveMessage: emitter.event,
postMessage: (message: any) => this._proxy.$postMessage(this._editorId, rendererId, message),
asWebviewUri: (uri: vscode.Uri) => this._asWebviewUri(uri),
};
}
private _asWebviewUri(localResource: vscode.Uri): vscode.Uri {
return asWebviewUri(this._webviewInitData, this._editorId, localResource);
}
}
@@ -618,7 +643,7 @@ export class ExtHostNotebookEditor extends Disposable implements vscode.Notebook
readonly id: string,
public uri: URI,
private _proxy: MainThreadNotebookShape,
private _webComm: ExtHostWebviewComm,
private _webComm: vscode.NotebookCommunication,
public document: ExtHostNotebookDocument,
private _documentsAndEditors: ExtHostDocumentsAndEditors
) {
@@ -721,6 +746,7 @@ export class ExtHostNotebookEditor extends Disposable implements vscode.Notebook
export class ExtHostNotebookOutputRenderer {
private static _handlePool: number = 0;
private resolvedComms = new WeakSet<ExtHostWebviewCommWrapper>();
readonly handle = ExtHostNotebookOutputRenderer._handlePool++;
constructor(
@@ -740,13 +766,19 @@ export class ExtHostNotebookOutputRenderer {
return false;
}
resolveNotebook(document: ExtHostNotebookDocument, comm: ExtHostWebviewCommWrapper) {
if (!this.resolvedComms.has(comm) && this.renderer.resolveNotebook) {
this.renderer.resolveNotebook(document, comm.getRendererComm(this.type));
this.resolvedComms.add(comm);
}
}
render(document: ExtHostNotebookDocument, output: vscode.CellDisplayOutput, outputId: string, mimeType: string): string {
let html = this.renderer.render(document, { output, outputId, mimeType });
return html;
}
}
export interface ExtHostNotebookOutputRenderingHandler {
outputDisplayOrder: INotebookDisplayOrder | undefined;
findBestMatchedRenderer(mimeType: string): ExtHostNotebookOutputRenderer[];
@@ -759,8 +791,9 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
private readonly _documents = new Map<string, ExtHostNotebookDocument>();
private readonly _unInitializedDocuments = new Map<string, ExtHostNotebookDocument>();
private readonly _editors = new Map<string, { editor: ExtHostNotebookEditor }>();
private readonly _webviewComm = new Map<string, { comm: ExtHostWebviewComm, onDidReceiveMessage: Emitter<any> }>();
private readonly _webviewComm = new Map<string, ExtHostWebviewCommWrapper>();
private readonly _notebookOutputRenderers = new Map<string, ExtHostNotebookOutputRenderer>();
private readonly _renderersUsedInNotebooks = new WeakMap<ExtHostNotebookDocument, Set<ExtHostNotebookOutputRenderer>>();
private readonly _onDidChangeNotebookCells = new Emitter<vscode.NotebookCellsChangeEvent>();
readonly onDidChangeNotebookCells = this._onDidChangeNotebookCells.event;
private readonly _onDidChangeCellOutputs = new Emitter<vscode.NotebookCellOutputsChangeEvent>();
@@ -854,6 +887,8 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
}
const renderer = this._notebookOutputRenderers.get(id)!;
this.provideCommToNotebookRenderers(document, renderer);
const cellsResponse: IOutputRenderResponseCellInfo<UriComponents>[] = request.items.map(cellInfo => {
const cell = document.getCell2(cellInfo.key)!;
const outputResponse: IOutputRenderResponseOutputInfo[] = cellInfo.outputs.map(output => {
@@ -890,6 +925,8 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
}
const renderer = this._notebookOutputRenderers.get(id)!;
this.provideCommToNotebookRenderers(document, renderer);
const cellsResponse: IOutputRenderResponseCellInfo<T>[] = request.items.map(cellInfo => {
const outputResponse: IOutputRenderResponseOutputInfo[] = cellInfo.outputs.map(output => {
return {
@@ -1035,19 +1072,36 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
return;
}
let webComm = this._webviewComm.get(editorId);
if (!webComm) {
webComm = new ExtHostWebviewCommWrapper(editorId, revivedUri, this._proxy, this._webviewInitData, document);
this._webviewComm.set(editorId, webComm);
}
if (!provider.provider.resolveNotebook) {
return;
}
let webComm = this._webviewComm.get(editorId)?.comm;
await provider.provider.resolveNotebook(document, webComm.contentProviderComm);
}
if (webComm) {
await provider.provider.resolveNotebook(document, webComm);
} else {
const onDidReceiveMessage = new Emitter<any>();
webComm = new ExtHostWebviewComm(editorId, revivedUri, this._proxy, onDidReceiveMessage, this._webviewInitData, document);
this._webviewComm.set(editorId, { comm: webComm, onDidReceiveMessage });
await provider.provider.resolveNotebook(document, webComm);
private provideCommToNotebookRenderers(document: ExtHostNotebookDocument, renderer: ExtHostNotebookOutputRenderer) {
let alreadyRegistered = this._renderersUsedInNotebooks.get(document);
if (!alreadyRegistered) {
alreadyRegistered = new Set();
this._renderersUsedInNotebooks.set(document, alreadyRegistered);
}
if (alreadyRegistered.has(renderer)) {
return;
}
alreadyRegistered.add(renderer);
for (const editor of document.allEditors()) {
const comm = this._webviewComm.get(editor.id);
if (comm) {
renderer.resolveNotebook(document, comm);
}
}
}
@@ -1182,12 +1236,8 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
return editor;
}
$onDidReceiveMessage(editorId: string, message: any): void {
let messageEmitter = this._webviewComm.get(editorId)?.onDidReceiveMessage;
if (messageEmitter) {
messageEmitter.fire(message);
}
$onDidReceiveMessage(editorId: string, forRendererType: string | undefined, message: any): void {
this._webviewComm.get(editorId)?.onDidReceiveMessage(forRendererType, message);
}
$acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEvent): void {
@@ -1226,12 +1276,11 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
private _createExtHostEditor(document: ExtHostNotebookDocument, editorId: string, selections: number[]) {
const revivedUri = document.uri;
let webComm = this._webviewComm.get(editorId)?.comm;
let webComm = this._webviewComm.get(editorId);
if (!webComm) {
const onDidReceiveMessage = new Emitter<any>();
webComm = new ExtHostWebviewComm(editorId, revivedUri, this._proxy, onDidReceiveMessage, this._webviewInitData, document);
this._webviewComm.set(editorId, { comm: webComm!, onDidReceiveMessage });
webComm = new ExtHostWebviewCommWrapper(editorId, revivedUri, this._proxy, this._webviewInitData, document);
this._webviewComm.set(editorId, webComm);
}
let editor = new ExtHostNotebookEditor(
@@ -1239,7 +1288,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
editorId,
revivedUri,
this._proxy,
webComm,
webComm.contentProviderComm,
document,
this._documentsAndEditors
);
@@ -1255,6 +1304,10 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
this._editors.get(editorId)?.editor.dispose();
for (const renderer of this._renderersUsedInNotebooks.get(document) ?? []) {
renderer.resolveNotebook(document, webComm);
}
this._editors.set(editorId, { editor });
}