Замороженный номер версии в User-Agent может уже не отражать правду. Сайты вычисляют реальную версию движка через feature-detection.
Ваша строка User-Agent сообщает Chrome/143.0.0.0. Это правда? Проверить это по самой строке мешают две разные вещи. Во-первых, сокращение User-Agent заморозило всё, что идёт после мажорной версии, до жёстко зашитого .0.0.0 — единственная часть версии, которая раньше двигалась с каждым обновлением, просто исчезла. Во-вторых — и это важнее — вся строка целиком есть то, что браузер говорит о себе сам, и любое расширение или средство автоматизации переписывает её одной строкой кода. Сайту, которому действительно нужно знать, какой релиз движка у вас запущен, приходится задавать совсем другой вопрос: не «что заявляет браузер», а «что он реально умеет». Это и есть определение версии через feature-detection — та же техника, что стоит за собственной проверкой ядра браузера BrowserInsight.
Ключевые выводы
- Определение версии через feature-detection проверяет наличие возможности, появившейся в одной версии движка, и отсутствие возможности, появившейся в следующей — так реальный движок «зажимается» в узкий диапазон.
- Результат всегда — диапазон, а не точный номер версии: функции выходят пачками, часть из них скрыта за флагами или бэкпортирована в старые сборки, поэтому техника способна очертить диапазон релизов, но не выдать полный четырёхчастный номер сборки, который показывает
chrome://version. - У любого такого детектора есть встроенный потолок: он способен проверять только те возможности, что были известны на момент составления таблицы, поэтому, как только браузер проходит все её тесты, честный вывод — «этот релиз или новее».
- Расхождение двух показаний несимметрично: вычисленная версия ниже заявленной в UA — это, как правило, просто устаревшая таблица, а вычисленная версия выше заявленной — то самое направление, которое действительно указывает на подмену: браузер может скрыть версию, но не может продемонстрировать возможности, которых у него нет.
- Те же самые проверки различают Blink, Gecko и WebKit ещё до того, как речь заходит о версии, потому что наборы возможностей этих движков расходятся независимо друг от друга.
Механизм: определение версии через границы возможностей
Каждый релиз движка браузера приносит набор наблюдаемых новых возможностей веб-платформы — CSS-свойство, метод JavaScript, интерфейс DOM. Большинство из них можно проверить прямо со страницы, без запроса разрешений и без действий пользователя: проверить, существует ли свойство, определён ли конструктор, принимается ли значение CSS.
Минимальная проверка на существование выглядит примерно так:
// `Intl.Locale.prototype.variants` появилось в Chromium 149.
const has149 = 'variants' in Intl.Locale.prototype;
// CSS-свойство `text-fit` появилось в Chromium 150.
const has150 = CSS.supports('text-fit', 'initial');
// has149 && !has150 -> перед нами Chromium 149.
Ни одна из этих строк ничего не загружает и не обращается к сети — они просто задают движку вопрос да/нет о его собственных возможностях. Суть в том, чтобы намеренно выбирать пары проверок: одна возможность точно появилась в определённом релизе, другая — точно ещё отсутствует в нём, но появится в следующем. Если первая проверка проходит, а вторая — нет, браузер оказывается «зажат» в диапазоне «этот релиз, но ещё не следующий». Прогнав достаточно таких пар по таблице известных релизов, можно подняться по этой лестнице до самого высокого релиза, все условия которого браузер полностью выполняет, — это и есть нижняя граница оценки реальной версии движка, совершенно независимая от того, что заявляет строка UA.
Именно так устроена собственная проверка ядра браузера BrowserInsight: она проходит по одной таблице релизов Chromium (начиная с Chromium 79) и по отдельной таблице для Firefox, тестируя известные для каждого релиза отличия в возможностях, и сообщает версию, на которой обрывается цепочка «пройдено» — вместе с версией, которую заявляет User-Agent, чтобы вы сразу видели, совпадают ли они. Обе проверки выше взяты прямо из этой таблицы.
Почему ответ — это диапазон, а не число
Матрица feature-detection никогда не сможет назвать точный билд так, как это делает chrome://version, — она способна очертить только диапазон релизов, и даже это с оговорками, которые стоит проговорить сразу:
- Функции выходят пачками, а не по одной на релиз, поэтому единственный пройденный тест редко сам по себе указывает на конкретную версию — обычно нужно несколько подтверждающих и опровергающих тестов вместе, чтобы сузить диапазон.
- Часть функций скрыта за экспериментальными флагами один или несколько релизов, прежде чем становится доступна всем по умолчанию, поэтому небольшая доля пользователей на конкретном релизе, до которых функция ещё не докатилась, может не пройти тест, который «должен был» пройти.
- Бэкпорты действительно случаются: связанная с безопасностью функция иногда попадает в более старый стабильный канал вне обычного графика релизов, из-за чего старая сборка может выглядеть на этом конкретном тесте новее, чем она есть.
Ничто из этого не ломает саму технику — просто честный вывод должен звучать как «этот браузер как минимум версии N», а не «этот браузер ровно версии N.N.N.N». Любой инструмент, включая собственную проверку ядра браузера на этом сайте, который подаёт feature-detection как способ разрешить точный патч-номер, преувеличивает то, на что метод на самом деле способен.
Почему у любого детектора есть потолок
Таблица feature-detection — это по сути снимок момента: она способна проверять только те возможности, что существовали и были известны на момент её составления. Как только браузер проходит все тесты в таблице — потому что он действительно новее самого свежего релиза, который таблица охватывает, — не остаётся ни одного теста, способного его «зацепить», и честный отчёт превращается в «этот релиз или новее», а не в точное число. Это не баг конкретной реализации, а структурное ограничение самого метода, и оно в равной мере касается собственной проверки ядра браузера на этом сайте. Таблицу нужно периодически обновлять по мере выхода новых релизов движков и появления новых отличительных функций для проверки — дорожная карта функций самого Chrome и его список уже вышедших функций остаются главным публичным источником данных о том, что и когда вышло, и именно за этим приходится следить тем, кто ведёт такую таблицу. Полезной перекрёстной проверкой сроков поддержки отдельной функции в разных движках служит caniuse.com — поэтому мейнтейнеры обычно не полагаются только на changelog одного производителя браузера.
В какую сторону читать расхождение
Когда показаний два, естественно их сравнить. Но два возможных расхождения вовсе не равноценны, и именно попытка считать их симметричными превращает проверку версии в инструмент, который записывает обычных людей в подменщики.
- Вычисленная версия ниже заявленной в UA — обычно вообще не ложь. Это тот самый потолок из предыдущего раздела, только на практике: браузер новее таблицы, проходит в ней все тесты и попадает в отчёт на верхнюю ступеньку. Проверка ядра браузера BrowserInsight намеренно не выносит вердикт о подмене в этом случае — на любой таблице, которую не обновляют в ту же неделю, когда выходит новый релиз, такой вердикт срабатывал бы на каждом полностью обновлённом посетителе.
- Вычисленная версия выше заявленной в UA — вот действительно подозрительное направление. Браузер может не афишировать свою версию, но не может продемонстрировать возможности, которыми не обладает. Функции, существующие только в более позднем релизе, под UA, заявляющим более ранний, означают, что строку переписали, а движок под ней продолжил говорить правду.
Есть и третий признак, который вообще не касается UA: складываются ли результаты «пройдено/не пройдено» в чистую лестницу. Настоящий движок проходит все релизы вплоть до своего собственного и заваливает все, что выше, без пропусков в середине. Пройденный более высокий релиз при заваленном более низком — это уже не версия, а форма, которой не даёт ни одна реальная сборка: она указывает на пропатченное или эмулированное окружение, а не на устаревший браузер. И поскольку эта проверка сравнивает результаты проб только друг с другом, она работает, даже если строка UA отсутствует, обезличена или откровенно бессмысленна.
Разные движки: та же техника, но ещё до версии
Feature-detection служит не только для определения версии — с её же помощью страница различает движки ещё до того, как вообще заходит речь о версии. Blink, Gecko и WebKit давно расходятся в том, когда и в каком порядке они внедряют экспериментальные и новые стандартизированные возможности — это расхождение началось задолго до того, как каждый из них добрался до своего текущего релиза. Функция, которая есть в Blink, но которой нет в Gecko, разделяет эти два движка независимо от того, какой именно релиз Chromium или Firefox сейчас запущен. За более глубоким разбором того, почему три движка расходятся именно так, — см. «Движки браузеров: Blink, Gecko и WebKit»; за разницей между движком рендеринга, который проверяет эта техника, и отдельным движком JavaScript, который идёт с ним в комплекте, — см. «Движок рендеринга vs движок JavaScript».
Стоит также точно понимать, что именно определяется. Chrome, Edge, Brave, Opera и любые другие браузеры на Chromium делят один и тот же движок Blink, поэтому проверка через feature-detection сообщает версию именно этого общего движка — «Chromium 143», а не версию продукта, обёрнутого вокруг него. «Chromium против Chrome» подробно объясняет, почему это различие существует и почему это не ошибка, а «Монокультура Chromium» — почему столько разных производителей браузеров в итоге стали делить один движок.
Почему этот метод стал полезнее, а не наоборот
Feature-detection — не костыль, который когда-то работал, а потом устарел с появлением лучших альтернатив: с изменением остальных сигналов о версии он стал ценнее, а не менее нужным.
- Строка User-Agent раньше менялась вместе с реальными обновлениями. До сокращения User-Agent UA браузера сообщал число, близкое к реальной патч-версии, так что вычисление версии по возможностям было скорее любопытным фактом. Теперь цифры версии в UA специально зафиксированы на
.0.0.0и вообще не меняются между патч-релизами — она и должна выглядеть статичной, и для любой актуальной установки это действительно так. Feature-detection — единственный оставшийся сигнал о версии, который не обнулён. - User-Agent Client Hints, а именно заголовок
Sec-CH-UA-Full-Version-Listи связанный с ним APInavigator.userAgentData, вернули точный номер версии — но это лишь третий способ, которым браузер сообщает то же самое утверждение о себе самом, доступный только по явному запросу, а не транслируемый по умолчанию. Это действительно полезный, более детальный канал, но его можно подделать теми же инструментами, что подделывают UA, — потому что и он, и UA исходят из самоотчёта браузера. - Расхождение между показаниями — теперь настоящий сигнал. Имея три относительно независимых источника — замороженный UA, заявленную в Client Hints версию и версию, вычисленную через feature-detection, — браузер, который на обоих каналах самоотчёта заявляет одно и то же, а ведёт себя как совершенно другая версия движка, демонстрирует именно то противоречие, о котором говорится в статье «Как обнаружить подмену user-agent». А feature-detection — единственное из трёх показаний, которое браузер не может просто заявить сам о себе.
Проверьте это на своём браузере
Проверка ядра браузера BrowserInsight прямо сейчас прогоняет эту пробу на вашем браузере: она показывает версию, вычисленную через feature-detection, рядом с версией, которую заявляет User-Agent, выносит вердикт, совпадают ли они, и выкладывает полный список «пройдено/не пройдено» по каждому релизу — так что видно, на какой ступеньке обрывается ваша лестница.
Если хотите сначала увидеть технику своими глазами, откройте DevTools и выполните CSS.supports('text-fit', 'initial'). true означает, что у вас Chromium 150 или новее; false — что вы либо ниже этого релиза, либо вообще на другом движке. В обоих случаях это одна ступенька лестницы, считанная прямо с движка, без всякого участия User-Agent.
Часто задаваемые вопросы
Может ли feature-detection показать точный номер сборки моего Chrome?
Нет. Она способна привязать ваш движок к конкретному релизу — например, «как минимум Chromium 149, но ещё не 150», — но хвостовые цифры сборки и патча, которые показывает chrome://version, остаются вне её досягаемости. Функции выходят по релизам, а не по патчам, поэтому точность на уровне патча в принципе не то, что может дать эта техника.
Работает ли это точно так же для Firefox и Safari?
Сама логика определения диапазона не зависит от движка, но таблицы функций нужно строить и поддерживать отдельно для каждого движка. Проверка ядра браузера BrowserInsight использует таблицу для Chromium (Blink) и таблицу для Firefox (Gecko); нумерация версий WebKit гораздо более шумная, и на этом сайте пока нет соответствующей матрицы, хотя тот же шаг определения движка применим и к нему.
Если браузер не проходит ни один тест из таблицы, значит ли это, что он старый?
Не обязательно — это может также означать, что браузер реально новее самого свежего известного релиза в таблице, поэтому ничто в таблице не отличает его от этого потолка. Прохождение всех тестов и провал всех тестов выглядят одинаково перед устаревшей таблицей; только конкретный узор из пройденных и непройденных тестов (а не сплошной успех или сплошной провал) подсказывает, с какой стороны от потолка таблицы вы находитесь на самом деле.
Почему бы просто не доверять версии из User-Agent Client Hints?
Client Hints действительно дают точный номер версии, но это по-прежнему утверждение браузера о самом себе, переданное по другому каналу, — а не независимо проверенное поведение. Feature-detection сравнительно труднее убедительно подделать, потому что для этого браузеру пришлось бы реально реализовать (или достаточно убедительно эмулировать) каждую функцию из таблицы вплоть до заявленного релиза, а не просто вернуть другую строку.
Заключение
Проверка через feature-detection отвечает на вопрос, на который замороженная строка User-Agent больше не может ответить честно: на какой версии движка на самом деле работает браузер, исходя из того, что он реально умеет, а не из того, чем он себя называет. Ответ приходит в виде диапазона с потолком, а не точного номера сборки — и это честное ограничение самого метода, а не изъян конкретной реализации. Его ценность не в точности, а в независимости. Из всех утверждений браузера о самом себе это единственный сигнал, который браузер не может просто заявить сам, — и именно поэтому его стоит сверять со всем остальным, что браузер о себе рассказывает.
Рекомендуем почитать:


