Hiya folks,
We use axe-core from within Lighthouse and we love it, however it takes a significant amount of time to run.
For example, the latest build tool ~3 mins to finish on this page https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
I've profiled things and have some findings that should help... (details to follow)

Here's the full recording. As you can see, it's taking 180s to complete. The purple on the bottom of each flame of javascript execution is layout. Aka forced layout aka reflow.
Zooming in:

And closer:

Two important pieces of information here. Where layout was forced and where it was invalidated.
Layout was forced at this line (from elements-from-point.js):
while ((current = doc.elementFromPoint(x, y)) && elements.indexOf(current) === -1 && current !== null) {
The elementFromPoint requires the layout of the page be recomputed if the layout potentially changed since last layout. And the "invalidation" is exactly that: where it was potentially changed.
That line looks like:
current.style.setProperty('pointer-events', 'none', 'important');
and there's another invalidation just a few lines later
for (i = previousPointerEvents.length; !!(d = previousPointerEvents[--i]); ) {
elements[i].style.setProperty('pointer-events', d.value ? d.value : '', d.priority);
}
While you and I know the pointer-events css property doesn't change the layout of objects on the page, unfortunately the browser is kinda dumb and considers this style change to be an invalidation.
And since we basically go back and forth between invalidation and forcing layout recomputing... that's how we end up in this pattern. Each time it happens it only takes like 3ms, but since it's done for all elements. that adds up.
More:
The fix is luckily pretty straightforward. Paul Lewis covers it in the above article.
Just do two loops, one where you invalidate and one where you measure.
(Or perhaps in your case it might be three.)
for (element of allElements) {
getTopElementFromPoint();
}
for (element of allElements) {
adjustPointerEventsForAllElements();
}
for (element of allElements) {
getTopElementFromPointAgain();
}
(I'm just guessing. I am not reading this code totally correctly. :)
Lastly, I realize that this work may not be even neccessary in a number of browsers..
document.elementsFromPoint() exists! (note the plural in there!) It's supported in everyone but safari.
Using this would mean you don't need to run this polyfill code at all. So.. that would actually be really straightforward to fix. :) (You could still fix the layout thrashing for Safari's sake if you want)
Good luck and let me know if you have any questions about this stuff!
Thank you so much for this! We knew the color contrast rule was the bottleneck for sure, just haven't gotten around to fixing it yet. This is immensely helpful!
@paulirish thanks for the excellent analysis. It turns out that we have already made some of the changes you talked about and these will be in the 2.1 release of the library (probably to be released in the next month or so)
On that MDN page, we get the following performance in Chrome

Specifically, we are using elementsFromPoint where this is available. We'll look into whether we can make Safari perform better using some of the info you provided.
Yay excellent news. Can I build 2.1 myself?
@paulirish we do our internal development on a private fork ATM. Part of our commitment to our paying customers is early access to enhancements.
ah okay! coolness.
if you can get me a build (.min.js is fine), I can profile it to see if there are any more big perf opportunities. But no worries if you can't :)
Email me at the email address in my profile and I'll see what I can do
Any news of 2.1 shipping? Can't wait to roll the latest into Lighthouse
We're getting close! I'm measuring the performance now and making sure any upgrades don't cause false positives or negatives before we commit to a release. At the very least, if our team is not satisfied with the current improvements I've been working on, we can ship the code already in our internal repository as Dylan mentioned before. I will update you as soon as I know more.
I'm also eagerly awaiting the 2.1 release for a performance fix. I'm willing to help wherever possible.
2.1.7 has been released! I'm going to close this issue, let us know if you still have any problems.
Nice. On a particularly tough page it looks like axe-core went from a runtime of 9.6s to 1.1s. :)
Thanks!
Whoa. Looks like you guys are splitting up all the tasks to yield very frequently:

That's totally awesome. Super performant. Great job on this!
So, noticed that performance in phantomjs is still pretty bad (presumably because it uses webkit which does not support getElementsFromPoint()). So I made a thing that uses chromium instead. I thought some folks might find it helpful. https://github.com/mfairchild365/the-axe-nightmare/
Most helpful comment
Whoa. Looks like you guys are splitting up all the tasks to yield very frequently:

That's totally awesome. Super performant. Great job on this!