Skip to content

Commit e18a778

Browse files
authored
Fixes #1692: setImmediate Implementation in browser RAL for json-rpc is not ideal. (#1702)
1 parent 713c221 commit e18a778

File tree

2 files changed

+101
-3
lines changed

2 files changed

+101
-3
lines changed

jsonrpc/src/browser/ril.ts

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,39 @@ interface RIL extends RAL {
130130
};
131131
}
132132

133+
export class QueueMicrotaskImpl implements Disposable {
134+
private isDisposed: boolean;
135+
constructor(callback: (...args: any[]) => void, ...args: any[]) {
136+
this.isDisposed = false;
137+
queueMicrotask(() => {
138+
if (!this.isDisposed) {
139+
callback(...args);
140+
}
141+
});
142+
}
143+
144+
dispose(): void {
145+
this.isDisposed = true;
146+
}
147+
}
148+
149+
export class PromiseImpl implements Disposable {
150+
private isDisposed: boolean;
151+
constructor(callback: (...args: any[]) => void, ...args: any[]) {
152+
this.isDisposed = false;
153+
Promise.resolve().then(() => {
154+
if (!this.isDisposed) {
155+
callback(...args);
156+
}
157+
}, () => {
158+
});
159+
}
160+
161+
dispose(): void {
162+
this.isDisposed = true;
163+
}
164+
}
165+
133166
const _textEncoder = new TextEncoder();
134167
const _ril: RIL = Object.freeze<RIL>({
135168
messageBuffer: Object.freeze({
@@ -166,8 +199,16 @@ const _ril: RIL = Object.freeze<RIL>({
166199
return { dispose: () => clearTimeout(handle) };
167200
},
168201
setImmediate(callback: (...args: any[]) => void, ...args: any[]): Disposable {
169-
const handle = setTimeout(callback, 0, ...args);
170-
return { dispose: () => clearTimeout(handle) };
202+
// Browser don't have setImmediate and setTimeout with 0 delay of 0 can cause problems
203+
// in webviews and similar environments due to throttling.
204+
if (typeof globalThis.queueMicrotask === 'function') {
205+
return new QueueMicrotaskImpl(callback, ...args);
206+
} else if (Promise !== undefined) {
207+
return new PromiseImpl(callback, ...args);
208+
} else {
209+
const handle = setTimeout(callback, 0, ...args);
210+
return { dispose: () => clearTimeout(handle) };
211+
}
171212
},
172213
setInterval(callback: (...args: any[]) => void, ms: number, ...args: any[]): Disposable {
173214
const handle = setInterval(callback, ms, ...args);

jsonrpc/src/browser/test/test.ts

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import * as assert from 'assert';
88
import { CancellationTokenSource, RequestMessage, RequestType0, ResponseMessage } from '../../common/api';
99
import { SharedArrayReceiverStrategy, SharedArraySenderStrategy } from '../../common/sharedArrayCancellation';
1010
import { BrowserMessageReader, BrowserMessageWriter, createMessageConnection } from '../main';
11-
import RIL from '../ril';
11+
import RIL, { QueueMicrotaskImpl, PromiseImpl } from '../ril';
1212

1313
function assertDefined<T>(value: T | undefined | null): asserts value is T {
1414
assert.ok(value !== undefined && value !== null);
@@ -106,4 +106,61 @@ suite('Browser IPC Reader / Writer', () => {
106106
const result = await promise;
107107
assert.ok(result, 'Cancellation failed');
108108
});
109+
110+
test('QueueMicrotaskImpl', async() => {
111+
let called = false;
112+
new QueueMicrotaskImpl(() => {
113+
called = true;
114+
});
115+
await new Promise((resolve) => setTimeout(resolve, 10));
116+
assert.strictEqual(called, true, 'QueueMicrotaskImpl callback not called');
117+
});
118+
119+
test('QueueMicrotaskImpl dispose', async() => {
120+
let called = false;
121+
const queueMicrotask = new QueueMicrotaskImpl(() => {
122+
called = true;
123+
});
124+
queueMicrotask.dispose();
125+
await new Promise((resolve) => setTimeout(resolve, 10));
126+
assert.strictEqual(called, false, 'QueueMicrotaskImpl callback called after dispose');
127+
});
128+
129+
test('PromiseImpl', async() => {
130+
let called = false;
131+
new PromiseImpl(() => {
132+
called = true;
133+
});
134+
await new Promise((resolve) => setTimeout(resolve, 10));
135+
assert.strictEqual(called, true, 'PromiseImpl callback not called');
136+
});
137+
138+
test('PromiseImpl dispose', async() => {
139+
let called = false;
140+
const promiseImpl = new PromiseImpl(() => {
141+
called = true;
142+
});
143+
promiseImpl.dispose();
144+
await new Promise((resolve) => setTimeout(resolve, 10));
145+
assert.strictEqual(called, false, 'PromiseImpl callback called after dispose');
146+
});
147+
148+
test('setImmediate', async() => {
149+
let called = false;
150+
RIL().timer.setImmediate(() => {
151+
called = true;
152+
});
153+
await new Promise((resolve) => setTimeout(resolve, 10));
154+
assert.strictEqual(called, true, 'setImmediate callback not called');
155+
});
156+
157+
test('setImmediate dispose', async() => {
158+
let called = false;
159+
const disposable = RIL().timer.setImmediate(() => {
160+
called = true;
161+
});
162+
disposable.dispose();
163+
await new Promise((resolve) => setTimeout(resolve, 10));
164+
assert.strictEqual(called, false, 'setImmediate callback called after dispose');
165+
});
109166
});

0 commit comments

Comments
 (0)