diff --git a/src/util/yield.ts b/src/util/yield.ts index a7626d60..73bfe499 100644 --- a/src/util/yield.ts +++ b/src/util/yield.ts @@ -10,9 +10,17 @@ export function enqueueTask(callback: () => void): void { // https://bugs.webkit.org/show_bug.cgi?id=327531. // Without this workaround, async mode would be effectively sync inside a web worker in Safari. // Instead, we very occasionally yield just so that really long-running async diffs will - // occasionally yield in Safari, while still eliminating 90% of the clamping in all environments. + // occasionally yield in Safari. That still eliminates 90% of the clamping where we have to fall + // back to setTimeout, and all of it where setImmediate exists. if (++enqueueTaskCalls % 10 == 0) { - setTimeout(callback, 0); + // Use setImmediate which yields without 1ms minimum in runtimes where it exists, setTimeout + // as fallback + const setImmediate = (globalThis as any).setImmediate; + if (typeof setImmediate === 'function') { + setImmediate(callback); + } else { + setTimeout(callback, 0); + } return; } diff --git a/test/diff/character.js b/test/diff/character.js index 42d78aa1..d623e890 100644 --- a/test/diff/character.js +++ b/test/diff/character.js @@ -108,12 +108,17 @@ describe('diff/character', function() { // other stuff to run - that is after all the point of async mode! const oldText = 'ax'.repeat(1000); const newText = 'ay'.repeat(1000); + // A 1ms interval should tick roughly once per millisecond while the diff runs. We compare + // against the elapsed time rather than a fixed count, since how long the diff takes depends + // on the machine. + const start = Date.now(); let ticks = 0; const interval = setInterval(() => { ticks++; }, 1); diffChars(oldText, newText, {callback: () => { clearInterval(interval); - expect(ticks).to.be.at.least(100); - done(); + const elapsed = Date.now() - start; + expect(ticks).to.be.at.least(elapsed / 2); + done(); }}); }); });