From Chrome 153 on 8 September, a new stable ships every two weeks. Any sentence containing "Chrome 150+" now ages twice as fast as it used to, and it was never accurate anyway: release notes announce features a version or more before the build enables them. So here is the replacement, asking your browser directly, one probe at a time.
Every row is a live probe against this browser, this build, this moment. Nothing is inferred from the user agent string and no version number is parsed anywhere on this page. Copy the readout at the bottom into a bug report and it says something true a year from now.
navigator.gpu can be present on a machine whose adapter request then fails, and the Prompt API can exist on a device with no model behind it and not enough memory to fetch one. Where a deeper check is cheap and safe, this page runs it and says which kind of answer you are getting.try/catch to catch, so a capability page that probed it on load would kill the tab of the visitor it was written to help. It sits behind a button in the list above, with this explanation next to it, and it stays that way until the crash is fixed upstream.This is the part that gets left out of feature-detection advice. The usual framing treats a probe as free, an in check with no consequence. Most are. A few touch a subsystem that may not be there, and the honest detector marks those, explains the risk in the interface, and makes the visitor opt in.
Property existence, CSS.supports, a constructor check. Run these on load without a second thought.
An adapter request or an availability enum. Real answers, so run them, but do not block first paint on them.
Anything that can start a download, open a permission prompt, or take the renderer down. Gate it behind an explicit action.
// version detection: wrong the moment you write it, and now wrong faster if (chromeVersion >= 152) { useCpuPerformance(); } // feature detection: still true after the next fourteen releases if ('cpuPerformance' in navigator) { seedQuality(navigator.cpuPerformance); } if (CSS.supports('background-clip', 'border-area')) { … } // and the one that needs a human to say yes first button.onclick = () => { // no try/catch here: a renderer crash is not an exception SpeechRecognition.available({ langs:['en-US'], processLocally:true }); };