From e0232259982e7112ba0646a163c1d975518fbf5f Mon Sep 17 00:00:00 2001 From: Arjun Chikara Date: Wed, 3 Jun 2026 00:11:17 +0530 Subject: [PATCH] feat(rule): gate sync rule-result resolution behind runTypeASync flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolving each rule's result via setTimeout(0) (Deque #1172) creates one macrotask transition per rule (~335). On large DOMs with advance ON these interleave with DevTools IPC (resource.getContent), ballooning the per-rule resolve from ~8ms to ~180ms and inflating axe.run from ~14s to ~85s. When axe._cache 'runTypeASync' is set (by a11y-engine-core run.js from the a11yCoreConfig flag), resolve synchronously to eliminate the contention. The flag defaults OFF, preserving the original deferred behaviour. On the OFF path, instrument the schedule-to-fire delay of each yield and accumulate {totalMs,count,maxMs} onto axe._typeASyncYield so every scan reports how much wall-clock the async resolve costs — the exact time the flag would recover. Read back into Type A telemetry by a11y-engine-core. Co-Authored-By: Claude Opus 4.8 (1M context) --- lib/core/base/rule.js | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/lib/core/base/rule.js b/lib/core/base/rule.js index f12cdbd6..046a8189 100644 --- a/lib/core/base/rule.js +++ b/lib/core/base/rule.js @@ -310,11 +310,39 @@ Rule.prototype.run = function run(context, options = {}, resolve, reject) { if (options.performanceTimer) { this._logRulePerformance(); } - // Defer the rule's execution to prevent "unresponsive script" warnings. - // See https://github.com/dequelabs/axe-core/pull/1172 for discussion and details. - setTimeout(() => { + // [a11y-core]: runTypeASync ON resolves synchronously; OFF keeps the + // deferred setTimeout(0) resolve (Deque #1172) and records yield delay. + if (cache.get('runTypeASync')) { resolve(ruleResult); - }, 0); + } else { + let scheduledAt; + try { + scheduledAt = window.performance.now(); + } catch { + scheduledAt = undefined; + } + setTimeout(() => { + try { + if (scheduledAt !== undefined) { + const yieldDelay = window.performance.now() - scheduledAt; + const yieldStats = axe._typeASyncYield || { + totalMs: 0, + count: 0, + maxMs: 0 + }; + yieldStats.totalMs += yieldDelay; + yieldStats.count += 1; + if (yieldDelay > yieldStats.maxMs) { + yieldStats.maxMs = yieldDelay; + } + axe._typeASyncYield = yieldStats; + } + } catch { + // ignore instrumentation failures + } + resolve(ruleResult); + }, 0); + } }).catch(error => { if (options.performanceTimer) { this._logRulePerformance();