src/vs/base/test/common/virtualScheduling/virtualTimeApi.ts

194 LOC · 163 covered · 31 uncovered · 22 ranges · 2121 concepts · 10 introducers · 1304 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- processor.ts ×17
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { IDisposable } from '../../../common/lifecycle.js';
7 > import { realTimeApi, TimeApi } from './timeApi.js';
8 > import { ROOT_TRACE, TraceContext } from './trace.js';
9 > import { VirtualClock } from './virtualClock.js';
10 >
11 > // V8 default `Error.stackTraceLimit` of 10 swallows everything past the
12 > // first async boundary in the stacks we capture for trace diagnostics.
13 > // Bump it so swimlane callers actually see the user code that scheduled a
14 > // timer rather than just the Promise wrapper.
15 > if (typeof Error.stackTraceLimit === 'number' && Error.stackTraceLimit < 50) {
16 > Error.stackTraceLimit = 50;
17 > }
18 >
19 > /** Virtual timer IDs are `IDisposable`s. Recover one from an opaque id. */
20 > function asDisposable(id: unknown): IDisposable | undefined { virtualTimeApi.ts ×1
21 > if (id === null || typeof id !== 'object') { return undefined; }
22 > const maybe = id as Partial<IDisposable>;
23 > return typeof maybe.dispose === 'function' ? id as IDisposable : undefined;
24 > }
26 > export interface CreateVirtualTimeApiOptions {
27 > /**
28 > * If `true`, `requestAnimationFrame` is faked: callbacks are scheduled
29 > * onto the virtual queue at `now + 16ms` and the resulting event hints
30 > * the embedding to use a real `requestAnimationFrame` so the host can
31 > * reflow before the callback runs. Useful for fixtures that need DOM
32 > * measurements after rAF callbacks.
33 > *
34 > * If `false` (default), `requestAnimationFrame` is left to the host.
35 > */
36 > readonly fakeRequestAnimationFrame?: boolean;
37 > }
38 >
39 > /**
40 > * Build a {@link TimeApi} that schedules every timer call into `clock`'s
41 > * virtual queue, capturing the current trace at schedule time so that
42 > * causal chains (`setTimeout` → `setTimeout`, etc.) are preserved.
43 > *
44 > * The returned API is suitable to install with {@link pushGlobalTimeApi},
45 > * which is what {@link runWithFakedTimers} does internally.
46 > */
47 > export function createVirtualTimeApi(
48 > clock: VirtualClock, virtualTimeApi.ts ×7
49 > options?: CreateVirtualTimeApiOptions,
50 > ): TimeApi {
51 >
52 > function virtualSetTimeout(handler: () => void, timeout: number = 0): IDisposable {
53 > const stack = new Error().stack; virtualTimeApi.ts ×1
54 > const trace = TraceContext.instance.currentTrace().child(`setTimeout(${timeout}ms)`, stack);
55 > return clock.schedule({
56 > time: clock.now + timeout,
57 > run: handler,
58 > source: { toString: () => 'setTimeout', stackTrace: stack },
59 > trace,
60 > });
61 > }
63 > function virtualClearTimeout(id: unknown): void {
64 > asDisposable(id)?.dispose(); virtualTimeApi.ts ×1
65 > }
67 > function virtualSetInterval(handler: () => void, interval: number): IDisposable {
68 > const stack = new Error().stack; virtualTimeApi.ts ×3
69 > const baseTrace = TraceContext.instance.currentTrace().child(`setInterval(${interval}ms)`, stack);
70 > let iter = 0;
71 > let disposed = false;
72 > let lastDisposable: IDisposable;
73 >
74 > const arm = (): void => {
75 > iter++;
76 > const myIter = iter;
77 > lastDisposable = clock.schedule({
78 > time: clock.now + interval,
79 > run: () => {
80 > if (disposed) { return; } virtualTimeApi.ts ×1
81 > arm(); // schedule the next tick first, so a throwing
82 > handler(); // handler doesn't kill the interval
83 > },
84 > source: { toString: () => `setInterval (iteration ${myIter})`, stackTrace: stack }, virtualTimeApi.ts ×3
85 > trace: baseTrace.child(`tick #${myIter}`),
86 > });
87 > };
88 >
89 > arm();
90 > return {
91 > dispose: () => {
92 > if (disposed) { return; }
93 > disposed = true;
94 > lastDisposable.dispose();
95 > },
96 > };
97 > }
99 > function virtualClearInterval(id: unknown): void {
100 > asDisposable(id)?.dispose(); virtualTimeApi.ts ×3
101 > }
103 > // A faux `Date` that returns virtual time from `now()` and uses virtual
104 > // time as the default constructor argument; everything else delegates.
105 > // The `Date` constructor is an exotic object whose call/construct
106 > // signatures aren't expressible without a structural mismatch — we go
107 > // through `unknown` for the tagging mutations rather than widening to
108 > // `any`.
109 > const OriginalDate = realTimeApi.Date;
110 > // `Date` is overloaded (zero-arg, one-arg, multi-arg). `ConstructorParameters`
111 > // only sees the last overload, so we type args as `unknown[]` and forward
112 > // them through a typed cast at the call site.
113 > function VirtualDate(this: unknown, ...args: unknown[]): unknown {
114 > if (!(this instanceof VirtualDate)) { virtualTimeApi.ts ×3
115 return new OriginalDate(clock.now).toString();
116 }
117 > if (args.length === 0) { virtualTimeApi.ts ×3
118 > return new OriginalDate(clock.now); virtualTimeApi.ts ×1
119 > }
120 > return new (OriginalDate as new (...a: unknown[]) => Date)(...args); virtualTimeApi.ts ×1
122 > // Static-property tagging. Use a typed `Record` view of the function virtualTimeApi.ts ×7
123 > // rather than `any`. We skip non-writable own properties (`length`,
124 > // `name` on a function would throw) and then explicitly set the few
125 > // statics callers reach for.
126 > const dateStatics = VirtualDate as unknown as Record<string, unknown>;
127 > const originalStatics = OriginalDate as unknown as Record<string, unknown>;
128 > for (const key of Object.getOwnPropertyNames(OriginalDate)) {
129 > const desc = Object.getOwnPropertyDescriptor(OriginalDate, key);
130 > if (desc && (desc.writable || desc.set)) {
131 > dateStatics[key] = originalStatics[key];
132 > }
133 > }
134 > dateStatics.now = () => clock.now;
135 > dateStatics.parse = OriginalDate.parse;
136 > dateStatics.UTC = OriginalDate.UTC;
137 > VirtualDate.prototype = OriginalDate.prototype;
138 >
139 > const api: TimeApi = {
140 > setTimeout: virtualSetTimeout as unknown as TimeApi['setTimeout'],
141 > clearTimeout: virtualClearTimeout,
142 > setInterval: virtualSetInterval as unknown as TimeApi['setInterval'],
143 > clearInterval: virtualClearInterval,
144 > Date: VirtualDate as unknown as DateConstructor,
145 > };
146 >
147 > // Expose the real setTimeout as `originalFn` on the virtual one. The
148 > // component-explorer host's polling loop reads this to escape virtual
149 > // time when waiting for renders to settle.
150 > (api.setTimeout as unknown as { originalFn: TimeApi['setTimeout'] }).originalFn = realTimeApi.setTimeout;
151 >
152 > if (options?.fakeRequestAnimationFrame) {
153 let rafIdCounter = 0;
154 const rafDisposables = new Map<number, IDisposable>();
155
156 api.requestAnimationFrame = ((callback: (time: number) => void) => {
157 const id = ++rafIdCounter;
158 const stack = new Error().stack;
159 const trace = TraceContext.instance.currentTrace().child('requestAnimationFrame', stack);
160 const d = clock.schedule({
161 time: clock.now + 16,
162 preferRealAnimationFrame: true,
163 run: () => {
164 rafDisposables.delete(id);
165 callback(clock.now);
166 },
167 source: { toString: () => 'requestAnimationFrame', stackTrace: stack },
168 trace,
169 });
170 rafDisposables.set(id, d);
171 return id;
172 }) as TimeApi['requestAnimationFrame'];
173
174 api.cancelAnimationFrame = ((id: number) => {
175 const d = rafDisposables.get(id);
176 if (d) {
177 d.dispose();
178 rafDisposables.delete(id);
179 }
180 }) as TimeApi['cancelAnimationFrame'];
181 }
183 > // Trace defaults: ensure handlers fired inside virtual time get the
184 > // current trace at *schedule* time, not at fire time. The processor
185 > // already wraps execution in `runAsHandler`, so when virtual events
186 > // fire inside the processor the trace is set correctly. This block is
187 > // just a safety net for callers that step the clock manually.
188 > void ROOT_TRACE;
189 >
190 > return api;
191 > }
193 > // Re-exported for convenience: many tests want to install both at once.
194 > export { pushGlobalTimeApi } from './globalTimeApi.js';