-
Notifications
You must be signed in to change notification settings - Fork 883
createGrid consumes excessive memory and crashes on pages with extremely large elements #3923
Description
Product
axe-core
Product Version
4.6.3
Latest Version
- I have tested the issue with the latest version of the product
Issue Description
Overview
Today, createGrid understands to avoid expanding grids for elements which are not visible:
axe-core/lib/commons/dom/create-grid.js
Lines 87 to 93 in d4522cd
| // filter out any elements with 0 width or height | |
| // (we don't do this before so we can calculate stacking context | |
| // of parents with 0 width/height) | |
| const rect = vNode.boundingClientRect; | |
| if (rect.width !== 0 && rect.height !== 0 && isVisibleOnScreen(node)) { | |
| addNodeToGrid(grid, vNode); | |
| } |
However, for elements which are partially visible and extremely large, this check isn't quite enough to avoid an undesirable explosion of grid size.
The motivating example is the Monaco editor. To render its main content area's background, it uses an element which is styled with width: 1e6; height: 1e6 (the linked playground page contains 3 editor areas, so it has 3 such elements).
The presence of these elements causes axe-core scans to eat up an enormous amount of memory (~2.5GB) and time (~4s wall time on my dev box), and often causes the whole target page to crash. Almost all of the perf hit comes from call stack createGrid > addNodeToGrid > loopGridPosition > loopNegativeIndexMatrix > loopNegativeIndexMatrix, where axe-core is allocating a separate JS object for each cell of a roughly 5000x5000 grid, almost every cell of which is not visible on screen.
I would imagine that making createGrid cooperate with elements like this would probably require coming up with a means to calculate the specific clipping of an element's clientRect(s) which are visible on screen and only add that to the grid, rather than adding the entire clientRect(s) whenever any part of the element is visible. I bet that's trickier than it sounds, though...
Expectation
axe-core should be able to scan the repro page in question with performance comparable to axe-core@4.4.3
Actual
Scanning the repro page takes ~2.5GB JS heap, ~4s wall time on my dev box, and often crashes the page entirely
How to Reproduce
- In Chromium, open the Monaco editor
- Open the F12 dev tools' "Performance" tab and start a trace
- Perform an axe-core scan (eg, from F12 dev console,
await fetch("https://cdnjs.cloudflare.com/ajax/libs/axe-core/4.6.3/axe.js").then(r => r.text()).then(src => eval(src)); await axe.run();) - Stop the performance trace and observe results
Additional context
Regression introduced between 4.4.3 to 4.5.0 (presumably with the addition of the new grid mechanism)