Skip to content

Commit 2933de0

Browse files
perf: Stop encoding messages in mobile (#3347)
The current approach for message passing in mobile encodes the JSON payloads into a byte array before injecting into the WebView. This was done by suggestion from @naugtur, but was known to be slow on mobile, especially with larger messages. More recently this caused performance degradations with Snaps that MetaMask is actively trying to ship to production causing us to revisit this. After discussion with @weizman we landed on the fact that simply JSON stringifying should be safe enough and much more performant.
1 parent ae4e83b commit 2933de0

File tree

6 files changed

+11
-22
lines changed

6 files changed

+11
-22
lines changed

packages/snaps-controllers/src/services/webview/WebViewMessageStream.test.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,11 @@ describe('WebViewMessageStream', () => {
2222
expect(await responsePromise).toBe(555);
2323

2424
expect(mockWebViewA.injectJavaScript).toHaveBeenCalledWith(
25-
`window.postMessage([123,34,116,97,114,103,101,116,34,58,34,98,34,44,34,100,97,116,97,34,58,34,83,89,78,34,125])`,
25+
`window.postMessage("{\\"target\\":\\"b\\",\\"data\\":\\"SYN\\"}")`,
2626
);
2727

28-
// Inject { target: "foo", data: 111 }
2928
mockWebViewA.injectJavaScript(
30-
`window.postMessage([123,34,116,97,114,103,101,116,34,58,34,102,111,111,34,44,34,100,97,116,97,34,58,49,49,49,125])`,
29+
`window.postMessage("{\\"target\\":\\"foo\\",\\"data\\":111}")`,
3130
);
3231

3332
const listener = jest.fn();

packages/snaps-controllers/src/services/webview/WebViewMessageStream.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { PostMessageEvent } from '@metamask/post-message-stream';
22
import { BasePostMessageStream } from '@metamask/post-message-stream';
33
import { isValidStreamMessage } from '@metamask/post-message-stream/dist/utils';
4-
import { assert, stringToBytes } from '@metamask/utils';
4+
import { assert } from '@metamask/utils';
55

66
export type WebViewInterface = {
77
injectJavaScript(js: string): void;
@@ -56,11 +56,9 @@ export class WebViewMessageStream extends BasePostMessageStream {
5656
data,
5757
});
5858

59-
// To prevent XSS, we encode the message before injecting it.
60-
// This adds significant performance overhead for larger messages.
61-
const bytes = new Uint8Array(stringToBytes(json));
59+
const encoded = JSON.stringify(json);
6260

63-
this.#webView.injectJavaScript(`window.postMessage([${bytes.toString()}])`);
61+
this.#webView.injectJavaScript(`window.postMessage(${encoded})`);
6462
}
6563

6664
// TODO: Either fix this lint violation or explain why it's necessary to

packages/snaps-controllers/src/test-utils/webview.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { bytesToString } from '@metamask/utils';
2-
31
import { WebViewMessageStream } from '../services/webview/WebViewMessageStream';
42

53
/**
@@ -9,9 +7,7 @@ import { WebViewMessageStream } from '../services/webview/WebViewMessageStream';
97
* @returns The decoded JSON as a string.
108
*/
119
export function parseInjectedJS(js: string) {
12-
const byteString = js.slice(19, -1);
13-
const bytes = new Uint8Array(JSON.parse(byteString));
14-
return bytesToString(bytes);
10+
return JSON.parse(js.slice(19, -1));
1511
}
1612

1713
/**
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"branches": 90.74,
33
"functions": 94.96,
4-
"lines": 90.86,
5-
"statements": 90.28
4+
"lines": 90.85,
5+
"statements": 90.27
66
}

packages/snaps-execution-environments/src/webview/WebViewExecutorStream.test.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
import { sleep } from '@metamask/snaps-utils/test-utils';
2-
import { stringToBytes } from '@metamask/utils';
32

43
import { WebViewExecutorStream } from './WebViewExecutorStream';
54

65
describe('WebViewExecutorStream', () => {
76
beforeEach(() => {
87
const addEventListener = jest.fn();
98
const postMessage = jest.fn().mockImplementation((message) => {
10-
const bytes = stringToBytes(message);
119
addEventListener.mock.calls.forEach(([_type, listener]) => {
12-
setTimeout(() => listener({ data: Array.from(bytes) }));
10+
setTimeout(() => listener({ data: message }));
1311
});
1412
});
1513
const mockWindow = {

packages/snaps-execution-environments/src/webview/WebViewExecutorStream.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import type { PostMessageEvent } from '@metamask/post-message-stream';
22
import { BasePostMessageStream } from '@metamask/post-message-stream';
33
import { isValidStreamMessage } from '@metamask/post-message-stream/dist/utils';
4-
import { bytesToString } from '@metamask/utils';
54

65
type WebViewExecutorStreamArgs = {
76
name: string;
@@ -65,12 +64,11 @@ export class WebViewExecutorStream extends BasePostMessageStream {
6564
// ignore.
6665
// eslint-disable-next-line no-restricted-syntax
6766
private _onMessage(event: PostMessageEvent): void {
68-
if (!Array.isArray(event.data)) {
67+
if (typeof event.data !== 'string') {
6968
return;
7069
}
7170

72-
const json = bytesToString(new Uint8Array(event.data as number[]));
73-
const message = JSON.parse(json);
71+
const message = JSON.parse(event.data);
7472

7573
// Notice that we don't check targetWindow or targetOrigin here.
7674
// This doesn't seem possible to do in RN.

0 commit comments

Comments
 (0)