Repository: microsoft/playwright
File: packages/injected/src/clock.ts (TypeScript)

Code before:
function fakePerformance(clock: ClockController, performance: Builtins['performance']): Builtins['performance'] {
  const result: any = {
    now: () => clock.performanceNow(),
  };
  result.__defineGetter__('timeOrigin', () => clock._now.origin || 0);
  // eslint-disable-next-line no-proto
  for (const key of Object.keys((performance as any).__proto__)) {
    if (key === 'now' || key === 'timeOrigin')
      continue;
    if (key === 'getEntries' || key === 'getEntriesByName' || key === 'getEntriesByType')
      result[key] = () => [];
    else
      result[key] = () => {};
  }
  return result;
}

function fakeAbortSignal(clock: ClockController, abortSignal: Builtins['AbortSignal']): Builtins['AbortSignal'] {
  const result: any = {
    ...abortSignal,
    timeout: (ms: number) => {
      const controller = new AbortController();
      clock.addTimer({
        delay: ms,
        type: TimerType.Timeout,
        func: () => controller.abort(new DOMException('This operation was aborted', 'AbortError')),
      });
      return controller.signal;
    },
  };
  return result;
}

export function createClock(globalObject: WindowOrWorkerGlobalScope): { clock: ClockController, api: Builtins, originals: Builtins } {
  const originals = platformOriginals(globalObject);
  const embedder: Embedder = {
    dateNow: () => originals.raw.Date.now(),
    performanceNow: () => Math.ceil(originals.raw.performance!.now()) as EmbedderTicks,
    setTimeout: (task: () => void, timeout?: number) => {
      const timerId = originals.bound.setTimeout(task, timeout);
      return () => originals.bound.clearTimeout(timerId);
    },
    setInterval: (task: () => void, delay: number) => {
      const intervalId = originals.bound.setInterval(task, delay);
      return () => originals.bound.clearInterval(intervalId);
    },
  };

  const clock = new ClockController(embedder);
  const api = createApi(clock, originals.bound);
  return { clock, api, originals: originals.raw };

Review comment (suggestion):
I am seeing `TimeoutError: signal timed out` in Chrome, `The operation timed out.` in Firefox and Safari.

Code after:

function fakePerformance(clock: ClockController, performance: Builtins['performance']): Builtins['performance'] {
  const result: any = {
    now: () => clock.performanceNow(),
  };
  result.__defineGetter__('timeOrigin', () => clock._now.origin || 0);
  // eslint-disable-next-line no-proto
  for (const key of Object.keys((performance as any).__proto__)) {
    if (key === 'now' || key === 'timeOrigin')
      continue;
    if (key === 'getEntries' || key === 'getEntriesByName' || key === 'getEntriesByType')
      result[key] = () => [];
    else
      result[key] = () => {};
  }
  return result;
}

function fakeAbortSignal(clock: ClockController, abortSignal: Builtins['AbortSignal'], browserName?: string): Builtins['AbortSignal'] {
  Object.defineProperty(abortSignal, 'timeout', {
    value(ms: number) {
      const controller = new AbortController();
      clock.addTimer({
        delay: ms,
        type: TimerType.Timeout,
        func: () => controller.abort(
            new DOMException(
                browserName === 'chromium' ? 'signal timed out' : 'The operation timed out.',
                'TimeoutError'
            )
        ),
      });
      return controller.signal;
    }
  });
  return abortSignal;
}

export function createClock(globalObject: WindowOrWorkerGlobalScope, config: InstallConfig = {}): { clock: ClockController, api: Builtins, originals: Builtins } {
  const originals = platformOriginals(globalObject);
  const embedder: Embedder = {
    dateNow: () => originals.raw.Date.now(),
    performanceNow: () => Math.ceil(originals.raw.performance!.now()) as EmbedderTicks,
    setTimeout: (task: () => void, timeout?: number) => {
      const timerId = originals.bound.setTimeout(task, timeout);
      return () => originals.bound.clearTimeout(timerId);
    },
    setInterval: (task: () => void, delay: number) => {
      const intervalId = originals.bound.setInterval(task, delay);
      return () => originals.bound.clearInterval(intervalId);
    },