Exploration: Better transferring of TypedArrays used in Webview.postMessage (#115664)

* Improve passing of ArrayBuffers to and from webviews

Fixes #115807

* Serialize and restore typed arrays too

This also makes it so that if you pass the same ArrayBuffer twice in an object, we use a single object on the receiver side too

* Fix spelling

* Require VS Code 1.56+
This commit is contained in:
Matt Bierner
2021-03-29 23:49:34 -07:00
committed by GitHub
parent 242bea8c8f
commit 3499f63dc1
20 changed files with 434 additions and 67 deletions

View File

@@ -3,11 +3,14 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { VSBuffer } from 'vs/base/common/buffer';
import { Emitter, Event } from 'vs/base/common/event';
import { URI } from 'vs/base/common/uri';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { normalizeVersion, parseVersion } from 'vs/platform/extensions/common/extensionValidator';
import { ILogService } from 'vs/platform/log/common/log';
import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService';
import { deserializeWebviewMessage } from 'vs/workbench/api/common/extHostWebviewMessaging';
import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview';
import type * as vscode from 'vscode';
@@ -28,6 +31,8 @@ export class ExtHostWebview implements vscode.Webview {
#isDisposed: boolean = false;
#hasCalledAsWebviewUri = false;
#serializeBuffersForPostMessage = false;
constructor(
handle: extHostProtocol.WebviewHandle,
proxy: extHostProtocol.MainThreadWebviewsShape,
@@ -43,6 +48,7 @@ export class ExtHostWebview implements vscode.Webview {
this.#initData = initData;
this.#workspace = workspace;
this.#extension = extension;
this.#serializeBuffersForPostMessage = shouldSerializeBuffersForPostMessage(extension);
this.#deprecationService = deprecationService;
}
@@ -104,7 +110,8 @@ export class ExtHostWebview implements vscode.Webview {
if (this.#isDisposed) {
return false;
}
return this.#proxy.$postMessage(this.#handle, message);
const serialized = serializeMessage(message, { serializeBuffersForPostMessage: this.#serializeBuffersForPostMessage });
return this.#proxy.$postMessage(this.#handle, serialized.message, ...serialized.buffers);
}
private assertNotDisposed() {
@@ -114,6 +121,49 @@ export class ExtHostWebview implements vscode.Webview {
}
}
export function shouldSerializeBuffersForPostMessage(extension: IExtensionDescription): boolean {
if (!extension.enableProposedApi) {
return false;
}
try {
const version = normalizeVersion(parseVersion(extension.engines.vscode));
return !!version && version.majorBase >= 1 && version.minorBase >= 56;
} catch {
return false;
}
}
export function serializeMessage(message: any, options: { serializeBuffersForPostMessage?: boolean }): { message: string, buffers: VSBuffer[] } {
if (options.serializeBuffersForPostMessage) {
// Extract all ArrayBuffers from the message and replace them with references.
const vsBuffers: Array<{ original: ArrayBuffer, vsBuffer: VSBuffer }> = [];
const replacer = (_key: string, value: any) => {
if (value && value instanceof ArrayBuffer) {
let index = vsBuffers.findIndex(x => x.original === value);
if (index === -1) {
const bytes = new Uint8Array(value);
const vsBuffer = VSBuffer.wrap(bytes);
index = vsBuffers.length;
vsBuffers.push({ original: value, vsBuffer });
}
return <extHostProtocol.WebviewMessageArrayBufferReference>{
$$vscode_array_buffer_reference$$: true,
index,
};
}
return value;
};
const serializedMessage = JSON.stringify(message, replacer);
return { message: serializedMessage, buffers: vsBuffers.map(x => x.vsBuffer) };
} else {
return { message: JSON.stringify(message), buffers: [] };
}
}
export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape {
private readonly _webviewProxy: extHostProtocol.MainThreadWebviewsShape;
@@ -132,10 +182,12 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape {
public $onMessage(
handle: extHostProtocol.WebviewHandle,
message: any
jsonMessage: string,
...buffers: VSBuffer[]
): void {
const webview = this.getWebview(handle);
if (webview) {
const { message } = deserializeWebviewMessage(jsonMessage, buffers);
webview._onMessageEmitter.fire(message);
}
}