Стоит ли создавать собственный браузер? Этот вопрос внутри Cloudflare поднимался каждые несколько месяцев на протяжении многих лет и неизменно вызывал долгие обсуждения с убедительными аргументами «за». Браузер — самое важное программное обеспечение, которым пользуются каждый день на компьютере, по сути операционная система интернета. Компания, миссия которой — помогать строить лучший интернет, естественно, не могла обойти стороной идею создать новый браузер.

Но каждый раз не удавалось найти баланс между технической сложностью такого предприятия и уникальностью проблем, которые оно решило бы. Поэтому идею откладывали снова и снова. До сих пор.

Совпало сразу несколько факторов: серия технических прорывов на платформе для разработчиков Cloudflare достигла зрелости именно в тот момент, когда бурный рост AI-агентов создал спрос на браузер нового типа.

Запуск WebAssembly (Wasm) в Workers стал по-настоящему зрелой технологией. Такие примитивы, как Dynamic Workers, Durable Objects на базе SQLite, RPC между воркерами, service bindings, более полная совместимость с Node.js и увеличенные лимиты открыли дорогу к значительно более амбициозным и сложным приложениям, которые раньше были просто невозможны.

Browser Run — продукт для headless-автоматизации браузеров — переживает бурный рост на волне интереса к AI. Агентам нужен браузер для выполнения многих задач, и без него они часто просто не справляются.

Проблема в том, что браузерные движки вроде Chromium создавались для людей, а не для агентов, и несут с собой накладные расходы, которые AI-моделям попросту не нужны. Они потребляют столько памяти и вычислительных ресурсов, что выделять каждому агенту собственный экземпляр становится непозволительно дорого — это ограничивает доступ к значительной части веба только самым продвинутым и дорогим моделям с большим объёмом параметрических знаний, отсекая множество других агентных приложений.

Правильнее давать всем агентам браузер, который отлично справляется с тем, что важно для AI-модели, даже если это означает урезание того, что нужно только людям. Например:

  • AI не важны вкладки, темы оформления, расширения браузера или синхронизация между устройствами. Важны количество токенов, размер контекстного окна, масштабируемость, производительность и стоимость.
  • Структурированный, машиночитаемый контент критичен, а визуальная безупречность и плавная прокрутка на 60 fps — нет. Агентам будет совершенно нормально, если парсинг CSS чуть неточен или рендеринг не идеально повторяет пиксель в пиксель.
  • Модель угроз для браузера, которым пользуется AI, иная. На первый план выходят новые проблемы — prompt injection и безопасность инструментов.

С учётом этих наблюдений, двенадцать недель назад в Cloudflare снова задали себе вопрос: строить ли собственный браузер? На этот раз ответ был единогласным: да!

Cloudflare анонсирует Kitesurf — новый браузер, работающий целиком на Workers, созданный специально для агентов и доступный бесплатно на этапе бета-тестирования в Browser Run.

Kitesurf заметно экономичнее Chromium по потреблению CPU и памяти для типичных агентных задач — скриншотов и извлечения HTML. Дальше — история о том, как его создавали. Материал получился техническим, но постараемся сделать его интересным.

С чего всё начиналось

Kitesurf начался так же, как и многие другие удачные идеи в Cloudflare: кто-то нашёл нечто интересное, и незаметно для остальных «заразил» команду на первый взгляд невозможной, но крайне привлекательной идеей.

Изначальным источником вдохновения стал obscura — headless-движок на Rust для AI-автоматизации, который работает «без Chrome, без Node.js, без зависимостей».

unnamed.png

С помощью AI-агента команда попыталась перенести его на Workers. Сначала получалось плохо. Но когда AI дали чёткий план и ясное определение успеха — достаточно детальное, чтобы агент мог итерироваться до бесконечности и при необходимости задавать вопросы — работа пошла. Впечатлённые этим (пока еще шатким) рабочим прототипом, в Cloudflare решили дать команде «поколдовать» дальше.

Ключевые архитектурные решения

Вот несколько решений, принятых ещё до начала разработки.

Тесты, тесты, тесты

Было понятно, что переход от прототипа к полноценному браузеру, полезному для промышленных задач при большой нагрузке, потребует множества итераций. AI использовали для ускорения процесса — это не секрет. Но как контролировать качество кода и результата в таком сложном проекте, применяя AI, без потери скорости? Ответ — предоставить как можно больше тестов.

Здесь на помощь пришли Web Platform Tests (WPT) — идеальный набор критериев успеха, который дал AI-агентам чёткие цели для оценки соответствия функций стандартам. Порядок и подбор функций для передачи агентам курировали люди, оставляя за собой архитектурную работу и проверку подходов, предложенных агентами.

Однако WPT-тесты покрывают не всё: они проверяют соответствие стандартам W3C, но не способность браузера отображать реальные сайты и взаимодействовать с ними. Чтобы закрыть этот пробел, добавили комбинацию интеграционного и визуального регрессионного тестирования — многошаговые тесты на Puppeteer на реальных сайтах, запускаемые одновременно на Chromium и Kitesurf, сравнивая не только утверждения (assertions), но и рендер на каждом шаге, чтобы выявить нежелательные отличия.

Rust везде, где возможно

Cloudflare давно работает над поддержкой WebAssembly (Wasm) в Workers. Это позволяет использовать высокопроизводительные пакеты на C, C++ и Rust, компилируя их в Wasm. Если использовать Emscripten с его многочисленными слоями мокированных зависимостей, скомпилированный бинарник получается громоздким и медленным.

Вместо этого выбрали нативный Rust, где это возможно, и компиляцию напрямую в WebAssembly через wasm-bindgen — так удаётся избежать ненужных слоёв эмуляции и работать как можно ближе к «железу», надёжно.

Обработка исключений

Браузер должен рендерить весь ненадёжный и порой недоброжелательный веб без потери отображаемой страницы, поэтому обработка исключений — это больше, чем гигиена кода: именно она позволяет приложению переживать плохие входные данные без падения.

С самого начала команда придерживалась одного правила: любая ошибка деградирует до пустого кадра или отсутствующего элемента, но никогда — до мёртвой сессии. Ловить сбои на каждой границе, по умолчанию откатываться к чему-то безопасному и пустому, и логировать достаточно для диагностики.

Изоляция

В отличие от браузера на ноутбуке, где посещаются доверенные сайты и допустимо частично делить ресурсы между ними, агент направляется туда, куда требует задача: к произвольному коду с произвольных источников.

Поэтому браузер строили на предположении, что каждая загрузка страницы — недоверенный вход, а каждая сессия начинается с чистого листа. Каждый компонент изолирован и имеет доступ только к ресурсам, строго необходимым для его функции.

BLOG-3466 3.png

Это идеально подходит под модель безопасности Cloudflare Workers, изначально построенную вокруг изоляции. Но платформа даёт лишь границу между изолятами. Тот же принцип пришлось внедрять и на уровне приложения — решая, к чему может обращаться каждый компонент, и следя, чтобы ничего не «протекало» между страницами, которым это не положено.

Отсутствие состояния, где возможно

Состояние — это то, что делает отказ дорогим: если нечего восстанавливать, то восстановление после сбоя — это просто запуск нового процесса и повтор запроса. Компонент без состояния одноразов и параллелен по своей природе: его можно убить в момент зависания, запустить тысячу экземпляров одновременно и масштабировать под спрос, а не держать «прогретым». Это идеально подходит для автоматизации, где нагрузка приходит всплесками, и самое дешёвое, что можно сделать — поднять работу, которая стоит ровно столько, сколько было использовано, и исчезает, как только выполнена. Короче: где компонент может быть без состояния, он должен быть без состояния.

Как это устроено

С хорошим планом, обширными тестами и удобным инструментарием команда двинулась дальше от начального прототипа. Вот общая схема жизненного цикла запроса Kitesurf, актуальная и сегодня:

BLOG-3466 4.png

Рассмотрим три главных компонента, из которых состоит Kitesurf: Engine, PageScript и PageRenderer.

Загрузка данных с источников

Чтобы отрендерить недоверенную веб-страницу, браузеру нужно загружать произвольные ресурсы — изображения, шрифты, CSS, JavaScript и Wasm-файлы — из интернета. Это одна из самых опасных операций, которые может выполнять браузер.

В Kitesurf этим занимается один единственный компонент — воркер SandboxOutbound, и ничто другое не может напрямую обращаться к сети: это обеспечивается через Dynamic Workers. Engine использует его для загрузки страницы, получая основной документ и его скрипты, а PageScript через него получает всё остальное: стили, изображения, шрифты и результаты собственных вызовов fetch() страницы.

SandboxOutbound применяется для соблюдения политики CORS, добавления заголовков, характерных для браузера, фильтрации ответов и хранения cookies каждой страницы в отдельном «хранилище». Всё, что не проходит политику, получает 403 — каждый компонент получает ровно тот доступ к сети, что ему нужен, и не больше.

BLOG-3466 5.png

Engine

Engine — единственный публично доступный компонент Kitesurf. Он обрабатывает WebSocket-соединения по протоколу Chrome DevTools Protocol (CDP) и HTTP REST API, отдаёт лендинг-страницу для внутреннего тестирования, и, что важнее всего, хранит состояние каждой сессии. Все остальные компоненты — без состояния.

BLOG-3466 6.png

Использование CDP даёт совместимость с клиентами: Puppeteer, Playwright, chrome-remote-interface и даже сама фронтенд-панель Chrome DevTools — направь их на Kitesurf, и всё просто заработает. Именно так работает и Browser Run (почему это важно, будет ясно позже).

Несмотря на название, Engine — на самом деле самый простой из компонентов Kitesurf. Дальше начинается самое интересное.

PageScript

PageScript — хороший пример возможностей новых функций Workers, в данном случае — Dynamic Workers. Без этой технологии Kitesurf был бы просто невозможен.

Вот упрощённая схема внутреннего устройства PageScript.

BLOG-3466 7.png

Каждая новая страница или изолированный процесс iframe (OOPIF) через Dynamic Workers поднимает долгоживущий изолят PageScript, который обслуживает сессию страницы — с чистым globalThis и объектом документа DOM.

Объект DOM затем заполняется результатами парсинга HTML-документа и выполнения всех JavaScript-скриптов. Для парсинга HTML и CSS используются части Blitz — модульного движка рендеринга, и Stylo — высокопроизводительного CSS-парсера Firefox; оба написаны на Rust.

Для каждого найденного тега <script> или .wasm-файла JavaScript- и WebAssembly-код выполняется в том же самом изоляте.

А как насчёт eval?

Что делать с eval? Это сложнее — по соображениям безопасности в Workers eval нативно до сих пор не поддерживается. Поднять для него отдельный изолят тоже нельзя — у него не будет доступа к globalThis.

Решение — использовать Boa JS, движок ECMAScript, написанный на Rust, компилируя и запуская его на Workers. Получается запуск среды выполнения внутри среды выполнения — не самое оптимальное решение, но оно работает достаточно хорошо для тех редких случаев eval, что встречаются в коде. Когда нативная поддержка eval появится в Workers, от Boa планируют отказаться.

PageRenderer

Этот компонент отвечает за генерацию конечных пикселей из вычисленных объектов страницы. Вот как это работает:

BLOG-3466 8.png

PageRenderer работает в цикле совместно с Engine Worker. Каждый раз, когда Engine требуется кадр, PageRenderer получает от PageScript объект страницы (так называемую «сцену»), загружает внутренние шрифты и изображения из Static Assets, растеризует всё в буфер изображения, а затем возвращает Engine готовый буфер в формате, который клиент может отобразить — JPEG/PNG или PDF.

Значительная часть этой «магии» реализована в другом модуле Blitz — blitz-paint, который в свою очередь использует Parley для формирования глифов из символов, выбора шрифтов и разбивки текста на строки.

Встроенный RPC-механизм Workers: одно приложение, множество изолятов

В Cloudflare Workers есть встроенная система удалённого вызова процедур (RPC), позволяющая вызывать методы других воркеров, передавать между ними объекты и вызывать методы у этих объектов. Не нужно думать о схемах API, типах или авторизации — достаточно вызвать remoteFunction(...params), и всё работает. Изоляция и ресурсы удалённого воркера при этом сохраняются, но без потери удобства локального доступа ко всем его функциям на JavaScript.

Kitesurf использует эту RPC-систему: Engine Worker вызывает renderFrame() из PageRenderer Worker через RPC одним вызовом и получает PNG в результате. Поскольку рендерер не хранит состояние страницы (только временный кэш), Engine может безопасно убить и перезапустить его при любом сбое или зависании RPC-вызова — это делает каждый запрос на рендер самодостаточным, повторяемым, а сам изолят — дешёвым и одноразовым.

Kitesurf проходит уже более 215 000 тестов WPT — и это число растёт

Kitesurf работает. Уже сейчас он проходит около 215 000+ тестов WPT, и каждую неделю добавляются сотни новых пройденных тестов. Вот динамика прохождения с момента начала проекта до последней версии:

BLOG-3466 9.png

Стоит отметить, что части браузера, важные для агентов (CSS, DOM, HTML, выделение, SVG, XHR), уже неплохо покрыты. Даже то, что для агентов не особо критично, например streams, теперь поддерживается достаточно прилично.

BLOG-3466 10.png

По производительности Kitesurf показывает неплохие результаты. Ниже — медианы пяти прогонов quick-action Browser Run на наборе из 14 URL, сравнивающие Chromium и Kitesurf.

МетрикаKitesurfChromium (тёплый пул)Kitesurf, относительно
CPU: скриншот380 мс1 173 мсв 3,1× меньше CPU, чем у Chromium
CPU: извлечение HTML229 мс877 мсв 3,8× меньше, чем у Chromium
Память: скриншот57,8 МиБ271,0 МиБв 4,7× меньше, чем у Chromium
Память: извлечение HTML39,4 МиБ273,7 МиБв 7,0× меньше, чем у Chromium
Общее время: скриншот1 148 мс637 мсв 1,8× медленнее Chromium
Общее время: извлечение HTML820 мс472 мсв 1,7× медленнее Chromium

По секундомеру побеждает Chromium — JIT-компилятор, уже «видевший» страницу, всегда обгонит холодный программный рендерер, и сегодня разница составляет около 1,7×. Большая часть этого разрыва связана с растеризацией и кодированием JPEG/PNG, над оптимизацией которых команда продолжает работать.

Но по памяти и CPU — тому, что на самом деле определяет счёт за использование, — Kitesurf выигрывает в 3–7 раз по сравнению с Chromium. Меньшее потребление памяти означает возможность запускать больше сессий, лучше масштабироваться и снижать издержки как для Cloudflare, так и для клиентов.

Главный тест — Kitesurf запускает Doom

Тестированию в архитектурных решениях уделялось особое внимание, но все знают: сколько бы тестов ни было, проект по-настоящему не завершён, пока на нём не запустится Doom. Вот как Kitesurf рендерит https://silentspacemarine.com/ — тот самый Doom из старого эксперимента Cloudflare несколько лет назад.

Попробовать уже сегодня в Browser Run

Kitesurf можно попробовать через Browser Run уже сегодня, бесплатно на этапе бета-тестирования, с ограничениями на аккаунт.

CDP-эндпоинт Browser Run теперь поддерживает Kitesurf как опцию, поэтому существующие клиенты — Puppeteer, Playwright, chrome-remote-interface или любой AI-агент, поддерживающий MCP и CDP, — заработают без изменений. Достаточно добавить параметр browser=kitesurf к эндпоинтам.

Например, чтобы использовать Kitesurf с Opencode, смотрите раздел Using with MCP clients (CDP) в документации разработчика и используйте следующую конфигурацию:

{
  "mcp": {
    "kitesurf": {
      "type": "local",
      "command": [
        "npx",
        "-y",
        "chrome-devtools-mcp@latest",
        "--wsEndpoint=wss://api.cloudflare.com/client/v4/accounts//browser-run/devtools/browser?browser=kitesurf",
        "--wsHeaders={\"Authorization\":\"Bearer \"}"
      ],
      "enabled": true
    }
  }
}

Ещё один способ использовать Kitesurf — через Quick Actions Browser Run. Также достаточно добавить browser=kitesurf к эндпоинту quick action. Например, для быстрого скриншота Wikipedia подойдёт такой запрос:

curl -X POST 'https://api.cloudflare.com/client/v4/accounts//browser-run/screenshot?browser=kitesurf' \
  -H 'Authorization: Bearer ' \
  -H 'Content-Type: application/json' \
  -d '{
    "url": "https://example.com"
  }' \
  --output "screenshot.png"

Playground Kitesurf с Chrome DevTools

Ещё один способ познакомиться с Kitesurf — воспользоваться публичным playground. Можно ввести любой URL и увидеть, как Kitesurf отрисовывает страницу, и взаимодействовать с ней.

Интересная особенность playground — встроенный Chrome DevTools в интерфейсе, позволяющий просматривать раскрытые DOM-элементы, читать сообщения консоли и наблюдать за сетевой активностью в процессе рендеринга страниц Kitesurf. Более того, реализованы необходимые CDP-инструкции для панели Memory, которая показывает объём памяти WebAssembly, занятой каждым изолятом, включая фреймы — так можно получить чёткое представление о том, сколько ресурсов потребляет каждая страница.

BLOG-3466 12.png

Все детали работы с Kitesurf через Browser Run — в документации разработчика.

Где Kitesurf хорош

На сегодняшний день Kitesurf правильно отображает такие страницы, как TodoMVC (vanilla, React, Vue, Angular, Preact), Wikipedia, Hacker News, блог Cloudflare и большую часть дашборда Cloudflare. Работа над улучшением Kitesurf и повышением процента пройденных WPT-тестов продолжается — это увеличит совместимость с более сложными веб-страницами.

Kitesurf отлично подходит для AI-агентов, которым нужно отображать страницы, но которые готовы принять компромиссы отказа от полнофункционального, пиксель-идеального Chromium. Он также хорошо подходит для автоматизаций и приложений, зависящих от одноразовых Quick Actions — таких как извлечение контента со страницы или генерация PDF и скриншотов — для совместимых сайтов.

Kitesurf можно представить как эфемерный, полностью изолированный движок без состояния, созданный для существования лишь на время выполнения задачи, который хорошо масштабируется под всплесковые, AI-ориентированные нагрузки.

Что Kitesurf пока не умеет

Если требуется проигрывать видео, рендерить WebGL, проходить проверку бот-защиты с реальными TLS-отпечатками или запускать десятиминутную аутентифицированную сессию с сохранением состояния — Kitesurf пока не подходящий вариант. В таких случаях стоит использовать браузер Browser Run по умолчанию, на базе Chromium.

Лучший способ понять, совместим ли конкретный сайт с Kitesurf — попробовать. Это можно сделать через API или быстрее — в публичном playground.

Стоит изучить панели DevTools и посмотреть, что происходит «за кулисами», обратив особое внимание на консоль и метрики памяти.

Что дальше

Kitesurf существует всего двенадцать недель, первый коммит был сделан в мае. Вот некоторые направления активной работы:

  • Более полное покрытие CDP. Kitesurf реализует подмножество протокола CDP — достаточное для покрытия требований большинства агентов и инструментов автоматизации, включая надёжную инспекцию DOM и сети — и его возможности продолжают расширяться, стремясь к максимальной полноте.
  • Точность рендеринга скриншотов и PDF, поскольку LLM часто работают лучше с изображением, чем с исходным текстом.
  • Покрытие WPT. Идёт активная работа по добавлению новых веб-API и прохождению большего числа тестов WPT на пути к production-готовности Kitesurf.
  • Эффективность. Постоянно ведётся мониторинг бенчмарков по CPU, памяти и общему времени выполнения, а также совместная работа с другими командами платформы разработчика над повышением экономичности и эффективности Kitesurf.

Заключение

Создание нового браузера, даже очень специфического, — задача одновременно важная и сложная, поэтому в описании технических деталей команда не стеснялась подробностей.

Kitesurf находится на раннем этапе разработки, но команда решила открыть доступ как можно раньше, чтобы учиться на отзывах пользователей. Планируются частые обновления, направленные на производительность, эффективность и совместимость.

Ещё один момент: Kitesurf планируется сделать открытым исходным кодом, как только это станет возможным — надеемся, что скоро. Цель — дать любому клиенту возможность развернуть собственную версию Kitesurf на своих аккаунтах, если он этого захочет.

Попробовать Kitesurf можно в playground, следить за обновлениями — в changelog, а обсудить с командой — в Discord.