[Bug] Background tokenizer throws "deadline is undefined" when the page has a non-spec requestIdleCallback shim (Safari 27 / iOS WKWebView)

Open Beginner friendly
#5,563 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
73/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Domain
frontend

Research direction

Start in vs/base/common/async, where _runWhenIdle passes the callback to requestIdleCallback; the issue also identifies deadline reads in vs/editor/common/model/textModelTokens.ts and editor/browser/gpu/taskQueue.ts. Reproduce with the supplied shim before Monaco loads, then check that background tokenization no longer throws and continues making progress.

Written by the indexing model from the issue text.

Description

Reproducible in vscode.dev or in VS Code Desktop?
  • Not reproducible in vscode.dev or VS Code Desktop
Reproducible in the monaco editor playground?
  • Not reproducible in the playground (requires a third-party shim on the page, see below)
Monaco Editor version

0.57.0

Description

We're honestly in doubt about what exactly is going on, but our error reports keep showing an uncaught error from background tokenization, from quite a few different users:

TypeError: undefined is not an object (evaluating 'e.timeRemaining')
  at DefaultBackgroundTokenizer._backgroundTokenizeWithDeadline (vs/editor/common/model/textModelTokens.js)
  at <anonymous> (textModelTokens.js)   // runWhenGlobalIdle((deadline) => …)
  • Browsers: Safari 27.0 / 27.0.1 on macOS, and WKWebView on iOS 18.7. Nothing from Chromium or Firefox.
  • It started right after we upgraded from 0.24 to 0.57.

The runner is invoked with no arguments, and its direct caller is window.setTimeout. Stable Safari doesn't ship requestIdleCallback by default (WebKit #285049 was reverted), so this can't be the native implementation. Our best guess is that something on those clients injects requestIdleCallback/cancelIdleCallback shims shaped like cb => setTimeout(cb) into the page: an extension, a userscript, or an in-app browser. We couldn't identify which. That shape is common in the wild, since many hand-rolled "polyfills" never pass an IdleDeadline.

Since both globals exist, _runWhenIdle in vs/base/common/async takes the native branch and hands runner to the shim, which calls it without a deadline. The deadline is read in textModelTokens.ts and in editor/browser/gpu/taskQueue.ts.

Arguably this is the shim's fault, but a one-line guard would make the editor resilient to a pattern that shows up a lot.

Repro (derived from reading _runWhenIdle)

Before monaco loads, run:

window.requestIdleCallback = (cb) => setTimeout(cb);
window.cancelIdleCallback = clearTimeout;

Then open a model longer than the viewport and type. Every content change reschedules background tokenization, and the reschedule throws.

Possible fix

Normalize the deadline in the native branch of _runWhenIdle. This covers every consumer:

const handle = targetWindow.requestIdleCallback(
  (deadline) => runner(deadline ?? { didTimeout: true, timeRemaining: () => 0 }),
  typeof timeout === 'number' ? { timeout } : undefined,
);

With timeRemaining: () => 0, the tokenizer does one ~1ms slice per idle callback and reschedules itself, so it keeps making progress instead of throwing.

Dominant language
JavaScript
Stars
46.9k
Forks
4.2k
Avg merge
1d 5h
Merged PRs (30d)
19

Getting set up

Open in Codespaces

Starts the project's dev container in your browser, under your own GitHub account.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from microsoft/monaco-editor

All issues in microsoft/monaco-editor

Similar issues

More JavaScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.