Escorp.Atom.Net.Browsing.WebDriver
0.9.0
dotnet add package Escorp.Atom.Net.Browsing.WebDriver --version 0.9.0
NuGet\Install-Package Escorp.Atom.Net.Browsing.WebDriver -Version 0.9.0
<PackageReference Include="Escorp.Atom.Net.Browsing.WebDriver" Version="0.9.0" />
<PackageVersion Include="Escorp.Atom.Net.Browsing.WebDriver" Version="0.9.0" />
<PackageReference Include="Escorp.Atom.Net.Browsing.WebDriver" />
paket add Escorp.Atom.Net.Browsing.WebDriver --version 0.9.0
#r "nuget: Escorp.Atom.Net.Browsing.WebDriver, 0.9.0"
#:package Escorp.Atom.Net.Browsing.WebDriver@0.9.0
#addin nuget:?package=Escorp.Atom.Net.Browsing.WebDriver&version=0.9.0
#tool nuget:?package=Escorp.Atom.Net.Browsing.WebDriver&version=0.9.0
Atom.Net.Browsing.WebDriver
Драйвер браузера через WebSocket-мост и расширение-коннектор. В отличие от Selenium/Puppeteer не использует CDP и отладочный API браузера: связь идёт через расширение браузера, а DOM-команды выполняются через изолированный канал вкладки. Пользовательский ввод при этом больше не синтетический: действия по селектору и точке идут через доверенные VirtualMouse и VirtualKeyboard в изолированном контексте дисплея.
Требования
- .NET SDK 10 — закреплён в
global.json(rollForward: latestFeature); проверка:dotnet --version→10.0.1xx+. - Node.js ≥ 22 и npm в PATH — обязательны для любой конфигурации сборки: в пакет входит браузерный
ExtensionRuntime(TypeScript), который собирается автоматически из.csproj. При отсутствии Node.js сборка остановится рано с понятной ошибкой вместо загадочногоexit code 127. - Опционально, только для real-browser интеграционных тестов: установленный браузер (см. «Поддерживаемые браузеры»), а на Linux —
xpra+xvfb. Выбор браузера — черезATOM_TEST_WEBDRIVER_BROWSERиATOM_TEST_WEBDRIVER_BROWSER_PATH; без них браузерные тесты пропускаются.
Быстрый старт после клона
# Сборка (Debug). На свежем клоне автоматически выполнится npm ci для ExtensionRuntime;
# повторные сборки используют уже установленные node_modules, пока не изменился lock-файл.
dotnet build Framework/Atom.Net.Browsing.WebDriver/Atom.Net.Browsing.WebDriver.csproj
# Тесты
dotnet test Tests/Atom.Net.Browsing.WebDriver.Tests/Atom.Net.Browsing.WebDriver.Tests.csproj
Публикация пакета (Release)
Release-конфигурация ссылается на пакеты Escorp.Atom.* плавающей версией *. Часть цепочки может быть ещё не опубликована на nuget.org (например, Escorp.Atom.Media.Audio), поэтому перед Release-сборкой наполни локальный фид .tmp/local-nuget-feed (source уже подключён в NuGet.config):
# Собирает цепочку зависимостей в порядке публикации и кладёт nupkg в .tmp/local-nuget-feed
bash .vscode/scripts/pack-webdriver-local-dependencies.sh
# Release-сборка (здесь же materialize'ится неподписанный Firefox-пакет для последующей
# подписи через AMO) и упаковка .nupkg + .snupkg, включая contentFiles расширений.
# Та же последовательность, что в publish-framework-package.sh и VS Code-задачах:
# сначала build, затем pack --no-build. Голый `dotnet pack -c Release` без предварительной
# сборки не используй — из-за переплетения GeneratePackageOnBuild/pack-таргетов проекта
# он завершается NU5026 (dll не найден) и не проверяет путь, по которому публикует команда.
dotnet build Framework/Atom.Net.Browsing.WebDriver/Atom.Net.Browsing.WebDriver.csproj -c Release -p:GeneratePackageOnBuild=false
dotnet pack Framework/Atom.Net.Browsing.WebDriver/Atom.Net.Browsing.WebDriver.csproj -c Release --no-build -p:GeneratePackageOnBuild=false
# Подпись и отправка Firefox XPI в AMO (нужны для release Firefox):
# workflow webdriver-firefox-sign.yml или локальная VS Code-задача `sign webdriver firefox xpi local`.
# Push на nuget.org (нужен NUGET_API_KEY или сохранённые креды); цепочку публикуй снизу вверх
# тем же publish-framework-package.sh, вебдрайвер — последним
NUGET_API_KEY=... bash .vscode/scripts/publish-framework-package.sh Framework/Atom.Net.Browsing.WebDriver/Atom.Net.Browsing.WebDriver.csproj
CI воспроизводит этот сценарий от чистого клона до проверки состава пакета джобой Pack (Escorp.Atom.Net.Browsing.WebDriver, Release) в .github/workflows/webdriver-tests.yml.
Подробности про ExtensionRuntime — в ExtensionRuntime/README.md.
Архитектура
.NET (WebBrowser)
│
├── BridgeServer (HTTP + WebSocket)
│ ├── Discovery / health / debug-event endpoint
│ ├── WebSocket каналы для обычного bridge-пути
│ ├── WSS transport для extension-backed channel
│ └── HTTPS managed delivery для Chromium system-policy path
│
└── TabChannel (по одному на вкладку)
↕ WebSocket
Extension (background.js)
│
└── content.js (по одному на вкладку)
↕ MutationObserver eval bridge
Страница (MAIN world)
Каждая вкладка получает изолированный transport-канал. Для обычного bridge-пути это локальный WebSocket, а для Linux Chrome Stable с system-policy bootstrap текущий browser-side channel поднимается через отдельный WSS transport. Команды выполняются независимо, как если бы каждая вкладка была отдельным процессом.
Текущий runtime-срез
В текущей ветке уже существует промежуточный runtime-слой, который подготавливает переход к полноценному BridgeServer, но пока не притворяется полной заменой эталонной реализации.
PageNavigationState
├── local BridgeMessage request/response envelope
├── local BridgeEvent queue
├── local callback producer для ExecuteScript
└── context propagation: windowId + tabId
WebPage
├── transport consumer
├── BridgeEventReceived
├── публичные события жизненного цикла
└── page-local callback/interception dispatch
Frame
└── публичные события жизненного цикла
WebWindow
├── bridge event queue
├── BridgeEventReceived
└── публичная цепочка ретрансляции lifecycle и interception
WebBrowser
├── bridge event queue
├── BridgeEventReceived
└── публичная цепочка ретрансляции lifecycle и interception
Сейчас staged runtime публикует локальный bridge-поток по цепочке transport → page → window → browser.
Для одной навигации active runtime публикует упорядоченный конверт:
RequestInterceptedResponseReceivedDomContentLoadedNavigationCompletedPageLoaded
Callback и CallbackFinalized тоже уже существуют в текущем runtime-срезе, но пока генерируются локальным transport-слоем только для распознанных вызовов подписанных callback-путей во время ExecuteScript.
Threading policy
Текущий runtime-срез сейчас считается потокобезопасной базой, но не runtime без блокировок в строгом смысле:
PageNavigationStateсериализует mutable navigation state (history,currentIndex) подSystem.Threading.Lock, а delivery очереди bridge events держит отдельно вConcurrentQueueWebBrowser,WebWindowиWebPageиспользуютConcurrentQueueдля внутренних потоков bridge-событий, поэтому параллельный drain больше не зависит от обычныхQueue<T>- публикация
CurrentWindowиCurrentPageупорядочена: новая сущность сначала попадает в snapshot-коллекцию, и только потом публикуется черезVolatile OpenWindowAsync,OpenPageAsyncи teardown-path координируются черезSystem.Threading.Lock, чтобыDisposeAsyncне гонялся с добавлением новых сущностей- browser-level
ClearAllCookiesAsyncfan-out'ит очистку по всем еще открытым окнам браузера, а window-levelClearAllCookiesAsyncделает то же самое по всем еще открытым страницам окна; если teardown уже начался, оба boundary должны fail-fast черезObjectDisposedException - после входа
WebBrowserилиWebWindowв disposed state поздниеOpen*,NavigateAsync,ReloadAsync, lookup-методы, cookie/geometry inspection и inspection текущей страницы должны либо завершиться до teardown, либо упасть сObjectDisposedException; тихое продолжение новой операции после dispose больше не считается допустимым поведением IWebBrowser,IWebWindow,IDomContextиIElementтеперь публикуютIsDisposedкак advisory snapshot текущего lifecycle state; это удобно для недорогих чтений и snapshot-логики, но не отменяет необходимость fail-fast guard'ов на boundary-методах, потому что между чтением флага и реальным действием остаётся обычное TOCTOU-окноWebPage,MainFrame,ElementиShadowRootтеперь тоже отражают disposal state через owner snapshot и сохраняют fail-fast поведение на boundary-вызовах после teardown- пока browser/window остаются живыми, их ownership-коллекции скрывают уже disposed children;
CurrentWindowпромотируется только при dispose текущего окна, аCurrentPageпромотируется только внутри текущего окна при наличии живой replacement-страницы; если текущая страница текущего окна удалена и внутри этого окна replacement больше нет, browser/window сохраняют последний опубликованный snapshot этой страницы до явногоOpenWindowAsyncилиOpenPageAsync, даже если в других окнах еще есть живые страницы, и current-boundary методы в этот промежуток fail-fast'ят черезObjectDisposedException; после полного teardown верхнего уровня ownership-коллекции очищаются, ноCurrentWindowиCurrentPageвсе равно сохраняют последний опубликованный snapshot; add/remove для lifecycle, callback и interception events остаются inert и non-throwing - browser/window lookup больше не опираются на late-dispose try/catch: active runtime сканирует concrete live snapshots и пропускает кандидатов, которые уже успели перейти в disposed state к моменту проверки
- публичные события жизненного цикла и внутренний
BridgeEventReceivedрассчитаны на concurrent delivery и subscriber churn, но гарантия порядка относится к одному transport stream; при параллельных producers события разных навигаций могут естественно чередоваться
Текущий baseline проверяется concurrency suite в Tests/Atom.Net.Browsing.WebDriver.Tests/WebDriverConcurrencyTests.cs: concurrent drain, mixed producer/consumer, publication stress для CurrentWindow / CurrentPage, lookup stress, dispose-race, navigate-dispose, mixed dispose+lookup, IsDisposed visibility и subscriber churn.
Поддерживаемые браузеры
| Браузер | Статус | Примечания |
|---|---|---|
| Brave | ✅ | Полная поддержка |
| Opera | ✅ | Полная поддержка |
| Opera GX | ✅ | Полная поддержка |
| Vivaldi | ✅ | Полная поддержка |
| Firefox | ⚠️ | На Linux Stable неподписанный profile-local bootstrap не гарантируется; stable-path требует подписанный XPI через ATOM_WEBDRIVER_FIREFOX_SIGNED_XPI_PATH, для живой проверки без подписи используйте Developer Edition или Nightly |
| Chrome | ✅ | Полная поддержка |
| Edge | ✅ | Полная поддержка |
| Yandex | ✅ | Полная поддержка |
API
Интерфейсы (Atom.Net.Browsing.WebDriver)
| Интерфейс | Назначение |
|---|---|
IDomContext |
DOM-операции: EvaluateAsync, WaitForElementAsync, GetElementAsync, GetElementsAsync, GetUrlAsync, GetTitleAsync, GetContentAsync, advisory IsDisposed |
IWebPage |
Страница: NavigateAsync, ReloadAsync, cookies, MainFrame, callback- и interception-события, публичные события жизненного цикла |
IFrame |
Фрейм: Page, Host, GetNameAsync, GetParentFrameAsync, наследует IDomContext, публичные события жизненного цикла |
IElement |
Элемент: ClickAsync, TypeAsync, PressAsync, GetPropertyAsync, GetAttributeAsync, advisory IsDisposed |
IWebWindow |
Окно: страницы, ActivateAsync, CloseAsync, навигация текущей вкладки, публичные события жизненного цикла, advisory IsDisposed |
IWebBrowser |
Браузер: окна, страницы, запуск, навигация, публичные события жизненного цикла, advisory IsDisposed |
Реализации (Atom.Net.Browsing.WebDriver)
| Класс | Назначение |
|---|---|
WebBrowser |
Точка входа: LaunchAsync, OpenWindowAsync, CurrentWindow, CurrentPage, browser-level lifecycle propagation |
WebWindow |
Окно браузера, владеет страницами, агрегирует lifecycle envelopes от своих вкладок |
WebPage |
Страница, делегирует DOM-операции к MainFrame и читает transport lifecycle envelopes |
Frame |
Runtime-реализация фрейма страницы |
Element |
Runtime-реализация DOM-элемента |
ShadowRoot |
Runtime-реализация ограниченного Shadow DOM-контекста |
Семантика поиска
IWebWindow.GetUrlAsyncиIWebWindow.GetTitleAsyncотражают толькоCurrentPageокна.IWebWindow.ActivateAsyncпубликует окно какCurrentWindowбраузера и синхронизирует этот переход с bridge-backed runtime, если у текущей страницы уже привязаны мостовые команды.IWebWindow.CloseAsyncзакрывает окно через bridge-backed команду при наличии моста и затем завершает локальный teardown окна; после закрытия браузер публикует следующий живой snapshot окна, если он существует.IWebBrowser.GetWindowAsync("current")возвращаетCurrentWindowнапрямую; любой другойstringищет окно по заголовку его текущей страницы и пропускает живые окна, у которых текущий удержанный snapshot страницы уже перешёл в disposed state к моменту проверки.IWebBrowser.GetWindowAsync(Uri)ищет окно по любой открытой странице внутри окна, а не только поCurrentPage.IWebBrowser.GetPageAsync(string/Uri)иIWebWindow.GetPageAsync(string/Uri)сканируют concrete live snapshots и пропускают дочерние snapshots, которые успели перейти в disposed state к моменту проверки, без late-dispose exception loop.IWebBrowser.GetPageAsync("current")иIWebWindow.GetPageAsync("current")возвращают текущий snapshot напрямую; literal titlecurrentне имеет приоритета над этим специальным token.IWebPage.GetFrameAsync("MainFrame")возвращаетMainFrameнапрямую; literal frame nameMainFrameне имеет приоритета над этим специальным token.- Если несколько живых сущностей совпадают по одному и тому же обычному title или url, порядок возврата определяется текущим live snapshot и не считается публичной гарантией.
- Cookie-surface в текущем runtime-срезе materialize-ит page-local состояние: page-level
SetCookiesAsyncсохраняет cookies в живом runtime snapshot,GetAllCookiesAsyncвозвращает их обратно,ClearAllCookiesAsyncочищает текущую страницу, а window/browser уровни fan-out'ят очистку по своим живым страницам и окнам. - Screenshot-surface в текущем runtime-срезе тоже stub-овая: page, main frame и element сейчас возвращают пустой payload, поэтому отсутствие байтов не нужно интерпретировать как частичную ошибку захвата.
Публичный жизненный цикл
Публичная lifecycle-surface теперь разделена на три отдельных события у IFrame, IWebPage, IWebWindow и IWebBrowser:
DomContentLoadedNavigationCompletedPageLoaded
WebLifecycleEventArgs несёт только публичный navigation context: Window, Page, Frame, Url и Title.
Пример подписки на NavigationCompleted страницы:
await using var browser = await WebBrowser.LaunchAsync(new WebBrowserSettings
{
Profile = WebBrowserProfile.Brave,
});
var page = browser.CurrentPage;
page.NavigationCompleted += (_, args) =>
{
Console.WriteLine($"{args.Title} -> {args.Url}");
Console.WriteLine($"Frame ref: {ReferenceEquals(args.Frame, page.MainFrame)}");
};
await page.NavigateAsync(new Uri("https://example.com"));
Пример browser-level aggregation по трем lifecycle этапам:
await using var browser = await WebBrowser.LaunchAsync(new WebBrowserSettings());
browser.DomContentLoaded += (_, args) => Console.WriteLine($"DOM ready: {args.Url}");
browser.NavigationCompleted += (_, args) => Console.WriteLine($"Navigation complete: {args.Url}");
browser.PageLoaded += (_, args) => Console.WriteLine($"Loaded: {args.Title}");
Доверенный ввод
Для пользовательского ввода драйвер использует доверенный путь OS/X11 через VirtualMouse и VirtualKeyboard, а не синтетическую DOM-диспетчеризацию внутри страницы. Это важно для anti-detect и для параллельной работы нескольких вкладок: у каждой вкладки свой канал, свой контекст дисплея и ввода, и нет зависимости от скрытой отладочной сессии или контура управления DevTools.
Поддерживаются операции уровня элемента, включая ClickAsync, HoverAsync, FocusAsync, TypeAsync, PressAsync, а также их человекоподобные варианты HumanityClickAsync и HumanityTypeAsync.
Все эти операции выполняются через доверенный контур ввода и DOM-мост поиска. Это значит:
- нажатия мышью и клавиш не зависят от синтетических DOM-событий
- ввод остаётся безопасным для параллельного выполнения при изолированных контекстах вкладки и дисплея
isTrustedиuserActivationтеперь определяются реальным путём ввода, а не диспетчеризацией на стороне страницы
DOM-мост по-прежнему отвечает только за поиск, ограничение области shadow-root, запросы геометрии и состояния и выполнение JS. Ввод больше не эмулируется через element.click(), синтетические MouseEvent и PointerEvent или прямое изменение value.
Пример:
var element = await page.WaitForElementAsync("#login-button");
await element!.ClickAsync();
await element.HumanityTypeAsync("demo@example.com");
await element.PressAsync(ConsoleKey.Enter);
Быстрый старт
// LaunchAsync материализует profile defaults и запускает реальный browser process,
// если Profile указывает на запускаемый binary.
await using var browser = await WebBrowser.LaunchAsync(new WebBrowserSettings
{
UseHeadlessMode = true,
Args = ["--no-sandbox"],
Profile = WebBrowserProfile.Brave,
});
// Работа с текущей вкладкой.
var page = browser.CurrentPage;
// DOM-операции.
var title = await page.GetTitleAsync();
var element = await page.GetElementAsync("#login-button");
await element!.ClickAsync();
// Выполнение JS в MAIN world.
var result = await page.EvaluateAsync<string>("document.cookie");
Механизм подключения
Текущее состояние ветки:
LaunchAsyncуже материализует внутренние profile files и стартует реальный browser process изProfile.BinaryPath, если binary действительно запускаемый.LaunchAsyncуже материализует внутренние profile files, поднимает liveBridgeServer, раскладывает Chromium-расширение сconfig.jsonв materialized profile, стартует реальный browser process изProfile.BinaryPathи возвращает браузер только после initial discovery bridge-bootstrap текущей вкладки.- Если в живой browser session уже есть bridge-bound страница,
OpenWindowAsyncиOpenPageAsyncидут через direct bridge-командыOpenWindowиOpenTabи возвращают новый window/page snapshot только после регистрации новой вкладки и привязки page/window bridge-команд; без bridge-bound source page эти операции остаются локальным staged fallback. - DOM/page model больше не является только пустым scaffold: поверх локального transport уже существуют protocol envelope, context propagation, lifecycle queues и публичные события жизненного цикла.
- Первый настоящий bridge-bootstrap уже подключён для Chromium-профилей: discovery endpoint живёт в
BridgeServer, расширение получает явныйsessionId, а текущиеWebPageиWebWindowперевязываются на реальныйtabIdиwindowIdпосле handshake. - Полной reference parity всё ещё нет: DOM
EvaluateAsync, element-команды и общий browser-side lifecycle поток пока не переведены на живой extension-backed transport.
Текущий staged runtime в ветке:
PageNavigationStatematerialize-ит localBridgeMessagerequest/response envelope дляNavigate,Reload,GetUrl,GetTitle,GetContentиExecuteScript- Для
NavigateиReloadtransport ставит в очередьRequestInterceptedиResponseReceived, а затем lifecycle-конвертDomContentLoaded -> NavigationCompleted -> PageLoaded - Для
ExecuteScripttransport может локально синтезироватьCallbackиCallbackFinalized, если выражение похоже на прямой вызов подписанного callback-пути WebPageчитает bridge stream из transport, поднимает page-level callback/interception events и прокидывает lifecycle вMainFrame,WebWindowиWebBrowserWebWindowиWebBrowserполучают тот же ordered bridge stream для одной transport-очереди; при параллельных producers interleave между разными навигациями допустим
Отдельная граница между стабильным контрактом и временной локальной эмуляцией зафиксирована в Framework/Atom.Net.Browsing.WebDriver/STAGED_RUNTIME_BOUNDARY.md.
Структура проверки bridge
Текущий bridge-срез проверяется несколькими слоями tests с разной зоной ответственности:
WebDriverBridgeHandshakeValidatorTestsдержит field validation и reject-code mapping на уровне handshake validatorWebDriverBridgeHandshakeSkeletonTestsтеперь является runnable contract-layer для validator и message-factory semantics, без дублирования transport-integration ветокWebDriverBridgeServerStateTestsиWebDriverBridgeServerStateSkeletonTestsдержат owner-state semantics, cleanup и health countersWebDriverBridgeServerSkeletonTestsдержит live transport integration для websocket handshake, tab lifecycle,SendRequestAsyncguard contract, inbound response validation, late-response semantics, первые per-command payload contracts дляGetTitle,GetUrl,GetContent,GetWindowBounds,ResolveElementScreenPoint,DebugPortStatusи richerDescribeElement, pending request completion and failure, timeout/cancel semantics, duplicate session, first-message policy, second handshake и close edge cases, а также secure transport coverage для WSS handshake, reject при неверном secret и health-среза secure transportBridgeTestHelpersцентрализует вспомогательные harness-методы для websocket и health в bridge-related test files
Внутри BridgeServer поверх raw SendRequestAsync уже существует минимальный command-aware helper layer для GetTitle, GetUrl, GetContent, GetWindowBounds, ActivateWindow, CloseWindow, ResolveElementScreenPoint, DebugPortStatus и DescribeElement. Это пока internal runtime-surface, а не публичный API верхнего уровня.
Поверх этого helper layer теперь существует отдельный internal BridgeCommandClient, а ещё выше появился page-bound internal PageBridgeCommandClient, который один раз связывает session/tab context и позволяет WebPage и WebWindow читать bridge-backed metadata и отправлять window-bound команды без утечки sessionId и tabId в публичный API.
Такое разбиение нужно сохранять и дальше:
- contract-level проверки должны оставаться рядом с validator, factory и owner state
- transport-specific handshake и teardown coverage не стоит возвращать в contract-level test files
Путь проверки в реальном браузере
Тесты реального браузера в Tests/Atom.Net.Browsing.WebDriver.Tests не включаются по умолчанию. Для них требуется явное переопределение окружения:
ATOM_TEST_WEBDRIVER_BROWSER— имя браузера:chrome,edge,brave,opera,opera-gx,vivaldi,yandexилиfirefoxATOM_TEST_WEBDRIVER_BROWSER_PATH— опциональный путь к исполняемому файлу, если нужен не автоматически найденный браузерATOM_TEST_WEBDRIVER_HEADLESS— опциональное переопределение headless-режима:trueилиfalse
Для branded Google Chrome Stable на Linux discovery-bootstrap теперь дополнительно материализует системный managed-policy target по пути /etc/opt/chrome/policies/managed/atom-webdriver-extension.json.
- локальный
chromium.managed-policy.jsonвнутри temp-профиля сохраняется как диагностический артефакт и источник JSON для публикации - если процесс уже имеет права записи в системный каталог, policy публикуется напрямую
- если прямой записи нет, runtime пробует best-effort публикацию через
sudo, используяATOM_WEBDRIVER_ROOT_PASSWORDлибо legacyESCORP_ROOT_PASSWORD - если системная публикация не удалась, это видно в
profile.jsonчерезbridge.managedPolicyPublishPathиbridge.managedPolicyDiagnostics
Важно разделять два независимых условия:
- system-policy publish и trust bootstrap для branded Chrome Stable на Linux всё ещё могут требовать системные права, если у процесса нет прямой записи в
/etc/opt/chrome/policies/managed - после того как policy уже опубликована и certificate trust уже устроен, сам browser-side transport больше не зависит от нового root-вмешательства: текущий post-delivery channel для этого профиля идёт через отдельный WSS transport
Именно этот transport blocker и был финальным live-блокером в текущей ветке: старый plain ws путь закрывался со стороны клиента до server-side upgrade, а переход на WSS снял этот сбой, не отменяя саму необходимость managed-policy bootstrap для branded Chrome Stable
Рекомендованный безопасный путь проверки для текущей ветки:
- Сначала прогнать обычный
Atom.Net.Browsing.WebDriver.Testsбез переопределений и убедиться, что bridge- и runtime-слой unit/integration тестов зелёный. - Затем включить только переопределения для реального браузера и прогнать
WebDriverRealBrowserIntegrationTestsлибо полный проект тестов. - Интерпретировать эти tests как проверку запуска браузера, materialization профиля, времени жизни процесса, discovery-bootstrap текущей вкладки и базовой page/window-surface, а не как уже полную DOM-паритетную замену локального transport.
Актуально подтверждённый live-срез для этого пути сейчас такой:
RealBrowserLaunchBootstrapsExtensionBackedDiscoverySurfaceподтверждает discovery-bootstrap и первичную привязку extension-backed surfaceLaunchAsyncMaterializesRequestedRealBrowserProfileAndHeadlessModeподтверждает materialization профиля и запуск branded Chrome Stable в ожидаемом режимеRealBrowserLaunchKeepsWindowAndPageSurfaceOperationalподтверждает рабочие page/window команды поверх уже поднятого extension-backed каналаRealBrowserWindowActivateAndCloseSurfaceStaysOperationalподтверждает, что window activate/close surface остаётся рабочим поверх того же transport-контура
Отдельно на серверном контрактном слое теперь зафиксировано, что secure transport:
- принимает WSS handshake только с корректным secret из
transportUrl - отвергает upgrade с неверным secret ещё до websocket acceptance
- публикуется в health payload отдельным
secureTransportблоком
Текущий живой smoke для реального браузера уже держится в коротком бюджете: ожидание завершения browser process после DisposeAsync в WebDriverRealBrowserIntegrationTests ужато до 5 секунд и остаётся зелёным на Chrome в headless-режиме.
Теперь тот же 5-секундный default выставлен и для BridgeSettings.RequestTimeout, но его всё равно нужно трактовать как часть мостового контракта, а не как обычный test-tuning:
- default живёт в
BridgeSettings.RequestTimeoutи теперь равен 5 секундам - значение сериализуется в handshake accept payload как
RequestTimeoutMs - server-side
BridgeServerиспользует тот же budget для pending request completion и timeout-маркировки - contract и handshake tests уже явно проверяют текущее значение по умолчанию
После перехода default BridgeSettings.RequestTimeout к 5 секундам безопасная проверка выглядит так:
- Держать отдельным контуром real-browser smoke для запуска браузера и базовой page/window-surface.
- Держать отдельным контуром handshake и contract assertions в
WebDriverProtocolSurfaceTests,WebDriverBridgeHandshakeSkeletonTests,WebDriverBridgeHandshakeValidatorTestsиBridgeTestHelpers, потому что они подтверждают уже именно протокольное значение default timeout. - Отдельно перепроверять
WebDriverBridgeServerSkeletonTests, где timeout-oriented сценарии уже используют локальные override-значения200msи потому проверяют server-side timeout semantics независимо от нового default.
Это важно, потому что live-срез BridgeServer уже проверяет server-side handshake и state model, а Chromium bootstrap теперь доведён до discovery-tab и direct page/window commands, но полный bridge-транспорт на стороне браузера всё ещё не доведён до полной reference parity.
Актуальный список оставшихся пробелов bridge-bootstrap и рекомендованный порядок следующих этапов реализации зафиксирован в Framework/Atom.Net.Browsing.WebDriver/STAGED_RUNTIME_BOUNDARY.md.
Ниже описан уже не только целевой, но и частично активный bootstrap path. Сейчас live runtime гарантированно покрывает discovery и initial page/window binding для Chromium, а DOM и richer command surface ещё остаются следующим этапом:
LaunchAsyncкопирует расширение во временную директорию и записывает внутреннийconfig.jsonдля transport bridge между драйвером и extensionbackground.runtime.jsпри старте читаетconfig.jsonчерезfetch(runtime.getURL("config.json"))- для Linux Chrome Stable с system-policy bootstrap
config.jsonможет нести отдельныйtransportUrl, и background runtime использует его как приоритетный browser-side channel - Расширение открывает discovery-вкладку и регистрирует WebSocket-каналы для каждой вкладки
content.jsустанавливает eval bridge через MutationObserver для выполнения JS в контексте страницы
Для текущего WSS-пути это означает следующее разделение ролей:
- HTTP остаётся точкой discovery, health и debug-event
- HTTPS managed delivery остаётся путём доставки manifest и CRX для system-policy сценария
- WSS используется как выделенный browser-side transport channel после доставки и bootstrap
- secret из
transportUrlвалидируется сервером ещё на этапе upgrade, до принятия websocket-сессии
Расширение
Единая кодовая база для Chrome (MV3) и Firefox (MV2):
ExtensionRuntime/— исходники runtime и packaging templatesExtension/иExtension.Firefox/вbin— build output каталоги расширений для Chrome MV3 и Firefox MV2
Новый scaffold ExtensionRuntime уже привязан к обычной сборке проекта: typecheck, сборка background runtime и синхронизация рабочих каталогов выполняются на уровне csproj, а build-owned артефакты живут в промежуточном каталоге и попадают в bin, а не в исходники
Полифилл const browser = globalThis.browser ?? globalThis.chrome; обеспечивает кроссбраузерность API.
Перехват запросов
В active runtime события Request и Response уже работают как live extension-backed interception surface на уровнях page/window/browser для fetch и других subresource-запросов. В headless Firefox Developer Edition этот срез подтверждён реальными тестами: Continue, Abort, Redirect, Fulfill, URL-pattern filtering, request header mutation и response header mutation проходят end-to-end через BridgeServer и ExtensionRuntime, включая bubbling между page/window/browser.
Это и есть текущий production-ready contract slice пакета для заявленной цели Firefox Dev headless. Он покрывает active request-side и response-side decision semantics, outer scope inheritance и per-tab/page-context locality. Неподтверждённые зоны теперь уже относятся не к самому fetch/subresource interception pipeline, а к отдельным сценариям вроде main-frame navigation fulfill и к кроссбраузерной parity вне Firefox Developer Edition.
В этот validated slice теперь входят и Client Hints: active payload contract сериализует high-entropy поля, ExtensionRuntime покрывает low/high entropy HTTP mutation и JS-side navigator.userAgentData, а live Firefox Developer Edition headless loopback test подтверждает исходящие Sec-CH-UA, Sec-CH-UA-Full-Version-List, Sec-CH-UA-Platform, Sec-CH-UA-Platform-Version, Sec-CH-UA-Mobile, Sec-CH-UA-Arch, Sec-CH-UA-Model и Sec-CH-UA-Bitness.
Подмена ответа для main_frame
- Описание ниже относится к отдельному special-case сценарию и не отменяет того факта, что fetch/subresource interception уже живой и подтверждённый.
- В active extension runtime request-side
FulfillAsyncдляmain_frameне считается production-ready и не должен использоваться как “честный” synthetic navigation response. - Firefox WebExtensions
webRequest.onBeforeRequestумеет толькоcancelилиredirectUrl, аfilterResponseDataработает уже на стадии response stream. Поэтому current runtime не заявляет main-frame fulfill parity без сетевого шага к origin. - Local bridge
/fulfill/{id}— это внутренний buffering contract между C# bridge и extension runtime для хранения prepared body, а не доказательство прямой browser-side main_frame delivery. Для request-sidemain_frameактивный runtime этот URL не consumes как fallback и fail-closed завершает запрос на browser webRequest boundary. - Из этого следует жёсткое правило: если для
main_frameкритично отсутствие сетевого запроса к узлу назначения, текущий extension-backed runtime такой fulfill не поддерживает. Fetch/subresource fulfill остаётся поддержанным, а main-frame navigation fulfill следует считать неподдерживаемым сценарием, а не fallback-фичей.
Прокси на уровнях браузера, окна и страницы
var proxy = new WebProxy("http://proxy.example.com:8080");
await using var browser = await WebBrowser.LaunchAsync(new WebBrowserSettings
{
Proxy = proxy,
});
var proxiedPage = await browser.CurrentWindow.OpenPageAsync(
new WebPageSettings { Proxy = proxy });
await proxiedPage.NavigateAsync(new Uri("https://example.com"));
var proxiedWindow = await browser.OpenWindowAsync(
new WebWindowSettings { Proxy = proxy });
await proxiedWindow.NavigateAsync(new Uri("https://example.com"));
Proxy с логином и паролем тоже поддерживается:
var authenticatedProxy = new WebProxy("http://proxy.example.com:8080")
{
Credentials = new NetworkCredential("user", "pass"),
};
Browser profiles and devices
Внешний orchestration-слой теперь можно строить целиком внутри пакета WebDriver, без вынесения profile/device surface в родительский контракт.
Запуск по профилю браузера
var profile = WebBrowserProfile.Brave;
await using var browser = await WebBrowser.LaunchAsync(new WebBrowserSettings
{
Profile = profile,
Args = ["--no-sandbox"],
Device = Device.DesktopFullHd,
});
WebBrowserProfile здесь отвечает только за выбор бинарника, канала и runtime profile path. Временные файлы профиля materialize-ятся внутри LaunchAsync уже после того, как собраны все WebBrowserSettings и Device-данные.
Автопоиск установленного браузера (включая Flatpak и Snap)
Профили браузеров сами ищут установленный бинарный файл: сначала проверяются нативные пути установки и имена в PATH с приоритетом запрошенного канала, а затем — sandboxed-установки. На Linux дополнительно опрашиваются Flatpak exports (/var/lib/flatpak/exports/bin/<app-id> и ~/.local/share/flatpak/exports/bin/<app-id>) и Snap launcher-скрипты (/snap/bin/<name>). Нативная установка всегда выигрывает у sandboxed. Задействованные app id: com.google.Chrome, com.microsoft.Edge, com.brave.Browser, com.opera.Opera, com.opera.OperaGX (flatpak-релиз Opera GX пока в разработке), com.vivaldi.Vivaldi, ru.yandex.Browser, org.mozilla.firefox, плюс snap-пакет firefox. Каталоги поиска можно переопределить через ATOM_WEBDRIVER_FLATPAK_SYSTEM_EXPORTS_DIR, ATOM_WEBDRIVER_FLATPAK_USER_EXPORTS_DIR и ATOM_WEBDRIVER_SNAP_BIN_DIR; префикс ~/ в путях-кандидатах раскрывается в домашний каталог пользователя.
Разрешённый способ установки доступен через WebBrowserProfile.InstallationKind (Native/Flatpak/Snap). От него зависят две ветки runtime-поведения на Linux:
- временный профиль материализуется не в общем
/tmp, а в каталоге, видимом внутри sandbox по тому же пути, что и на хосте (~/.var/app/<app-id>/atom-webdriver/<guid>для Flatpak и~/snap/<name>/common/atom-webdriver/<guid>для Snap), потому что Flatpak и Snap монтируют приватный/tmp; - для sandboxed stable Chromium-профилей bootstrap расширения не использует системные managed policy хоста (
/etc/opt/...), которые внутри sandbox не читаются, а остаётся в profile-seeded режиме — так же, как при opt-inUseRootlessChromiumBootstrap.
При materialization драйвер теперь не ограничивается одним profile.json: под каждый browser family заранее раскладываются automation-oriented profile files. Для Chromium-профилей создаются Default/Preferences, Local State и First Run с отключёнными welcome/FRE, background networking, sync, autofill, translate, Safe Browsing и password-manager фичами. Для Firefox создаётся user.js с отключёнными telemetry/new tab/discovery/pocket/GPU-heavy флагами и с базовой automation-конфигурацией. Browser-specific ветки тоже учитываются: например, Edge получает anti-FRE disable-features, а Vivaldi — pre-seeded startup/welcome prefs.
Для Firefox на Linux это ограничение принципиальное для release Stable: неподписанное profile-local расширение обычно не активируется. Драйвер намеренно не переводит этот bootstrap на Marionette, потому что такой обход меняет JS-видимое automation state страницы, включая navigator.webdriver.
Для Firefox Stable runtime теперь поддерживает отдельный install overlay: если задать путь к подписанному XPI через ATOM_WEBDRIVER_FIREFOX_SIGNED_XPI_PATH, драйвер поднимает локальную shim-installation внутри materialized profile, кладёт туда distribution/policies.json, force-install'ит signed XPI через ExtensionSettings и передаёт session config через 3rdparty -> Extensions -> <addon-id>, который background runtime читает из storage.managed. Сам подписанный XPI runtime не модифицирует, поэтому пакет должен быть заранее собран и подписан из той же версии extension source.
Если на машине уже существует глобальный /etc/firefox/policies/policies.json c тем же addon id, Firefox Stable может предпочесть системную policy и проигнорировать overlay policy из install shim. Runtime теперь сохраняет такое пересечение в bridge.managedPolicyDiagnostics.detail, но сам конфликт нужно устранять на уровне host policy: удалить или синхронизировать системную запись.
Если signed XPI не задан, Linux Firefox Stable остаётся в диагностическом profile-local режиме, а для живой проверки extension-backed bootstrap по-прежнему рекомендуются Firefox Developer Edition или Nightly.
Автоматическая упаковка и подпись Firefox XPI
Сборка Framework/Atom.Net.Browsing.WebDriver теперь умеет автоматически материализовать unsigned Firefox package в obj/<Configuration>/<TargetFramework>/extension-packages/firefox/atom-webdriver-firefox-<version>-unsigned.zip. Для локального прогона можно использовать задачу VS Code package webdriver firefox xpi или прямой dotnet build -c Debug -p:CreateFirefoxExtensionPackageOnBuild=true. Для Release в этом репо по-прежнему нужны доступные приватные NuGet-источники WebDriver-зависимостей.
Для автоматической подписи добавлен ручной workflow .github/workflows/webdriver-firefox-sign.yml. По умолчанию он собирает Debug, чтобы не зависеть от внешнего feed, ожидает Mozilla AMO API credentials в секретах MOZILLA_AMO_JWT_ISSUER и MOZILLA_AMO_JWT_SECRET, подписывает Firefox build output через web-ext sign --channel unlisted, выгружает готовый signed XPI как artifact и при желании может сразу запустить live smoke на Linux Firefox Stable через ATOM_WEBDRIVER_FIREFOX_SIGNED_XPI_PATH.
Для локальной подписи добавлен helper Framework/Atom.Net.Browsing.WebDriver/ExtensionRuntime/scripts/sign-firefox-package.sh. Он повторяет тот же pipeline, но читает credentials из переменных окружения WEB_EXT_API_KEY и WEB_EXT_API_SECRET, сам вызывает dotnet build -p:CreateFirefoxExtensionPackageOnBuild=true, затем отправляет текущую Firefox-версию в AMO через web-ext sign без встроенного скачивания и сам доводит процесс до конца через AMO API: ждёт нужную unlisted-версию по текущему manifest.version, скачивает signed XPI в obj/<Configuration>/<TargetFramework>/extension-packages/firefox/signed и умеет восстановиться, если тот же upload уже был отправлен раньше. Если нужно, окно ожидания можно переопределить через --approval-timeout <ms>. Его можно запускать напрямую или через npm --prefix Framework/Atom.Net.Browsing.WebDriver/ExtensionRuntime run sign:firefox -- --configuration Debug --target-framework net10.0. Для удобства в VS Code добавлена задача sign webdriver firefox xpi local, а workflow подписи теперь использует тот же helper, чтобы локальный и CI-пути не расходились.
Это не локальная CA-подпись: Firefox Stable доверяет только подписи, выданной Mozilla signing service через AMO API. Поэтому automation pipeline может быть полностью нашим, но шаг trust/signing всё равно проходит через Mozilla credentials.
Поверх browser-family defaults в materialized profile теперь также подмешиваются runtime overrides из WebBrowserSettings и Device: Chromium effective arguments получают --proxy-server, --lang и --user-agent, а Firefox user.js получает proxy prefs, general.useragent.override, intl.accept_languages и browser.privatebrowsing.autostart, если это было запрошено настройками запуска.
Применение device preset ко всем новым вкладкам
await using var browser = await WebBrowser.LaunchAsync(new WebBrowserSettings
{
Profile = WebBrowserProfile.Brave,
Device = Device.Pixel2,
});
Применение device preset только к новой вкладке
var page = await browser.CurrentWindow.OpenPageAsync(new WebPageSettings
{
Device = Device.MacBookPro14,
});
await page.NavigateAsync(new Uri("https://example.com"));
Тонкая настройка полного fingerprint-профиля
var device = Device.DesktopFullHd;
device.Locale = "de-DE";
device.Timezone = "Europe/Berlin";
device.Languages = ["de-DE", "de", "en-US"];
device.Geolocation = new GeolocationSettings
{
Latitude = 52.5200,
Longitude = 13.4050,
Accuracy = 20,
};
device.NetworkInfo = new NetworkInfoSettings
{
EffectiveType = "wifi",
Type = "wifi",
Downlink = 80,
Rtt = 12,
};
var page = await browser.CurrentWindow.OpenPageAsync(new WebPageSettings
{
Device = device,
});
Device теперь описывает не только viewport/mobile/touch, но и основные fingerprint-группы: UserAgent, Platform, Locale, Timezone, Languages, Screen, ClientHints, Geolocation, NetworkInfo, WebGL, WebGLParams, SpeechVoices, VirtualMediaDevices, а также privacy/noise-переключатели вроде CanvasNoise, AudioNoise, FontFiltering, DoNotTrack и GlobalPrivacyControl.
Навигация с локальной HTML-подстановкой
await page.NavigateAsync(
new Uri("https://example.com"),
new NavigationSettings
{
Html = "<html><body>Hello from local fulfill</body></html>",
});
Матрица возможностей: Chromium vs Firefox
| Возможность | Chromium (MV3) | Firefox (MV2) |
|---|---|---|
| User-Agent (HTTP) | declarativeNetRequest |
webRequest.onBeforeSendHeaders |
| User-Agent (JS) | MAIN world injection | MAIN world injection |
| Cookies (Set-Cookie capture) | webRequest.onHeadersReceived (non-blocking, extraHeaders) |
webRequest.onHeadersReceived (blocking) |
| Cookies (Set-Cookie strip) | declarativeNetRequest |
webRequest.onHeadersReceived |
| Cookies (HTTP injection) | declarativeNetRequest Cookie header |
webRequest.onBeforeSendHeaders Cookie header |
| Cookies (JS) | In-memory shim | In-memory shim |
| localStorage / sessionStorage | Namespace shim | Namespace shim |
| IndexedDB | Prefix shim | Prefix shim |
| Cache API | Prefix shim | Prefix shim |
| Screen | MAIN world override | MAIN world override |
| Canvas fingerprint | Noise injection | Noise injection |
| WebGL vendor/renderer | getParameter patch |
getParameter patch |
| WebRTC | Block / relay-only | Block / relay-only |
| Timezone | Intl + Date override |
Intl + Date override |
| Geolocation | navigator.geolocation override |
navigator.geolocation override |
| Font fingerprint | document.fonts.check() filter |
document.fonts.check() filter |
| AudioContext fingerprint | OfflineAudioContext.startRendering noise |
OfflineAudioContext.startRendering noise |
| Hardware concurrency | navigator.hardwareConcurrency override |
navigator.hardwareConcurrency override |
| Device memory | navigator.deviceMemory override |
navigator.deviceMemory override |
| Battery API | navigator.getBattery() fake (charging, level=1) |
navigator.getBattery() fake (charging, level=1) |
| Permissions API | permissions.query() → "prompt" |
permissions.query() → "prompt" |
| Client Hints (HTTP) | declarativeNetRequest Sec-CH-UA-* |
webRequest.onBeforeSendHeaders Sec-CH-UA-* |
| Client Hints (JS) | navigator.userAgentData override |
navigator.userAgentData override |
| Network Information | navigator.connection override |
navigator.connection override |
| Speech Synthesis | speechSynthesis.getVoices() override |
speechSynthesis.getVoices() override |
| Media Devices | enumerateDevices() fake set |
enumerateDevices() fake set |
| Virtual Media Devices | Tab-local alias enumerateDevices() + routed getUserMedia() к browser-visible audio/video устройствам; audio output alias-only |
Tab-local alias enumerateDevices() + routed getUserMedia() к browser-visible audio/video устройствам; audio output alias-only |
Для Client Hints active readiness now means end-to-end, а не только наличие API surface: .NET payload contract, background request-header mutation, content/main-world navigator.userAgentData override и live Firefox Developer Edition wire proof закрыты одновременно.
| WebGL params | getParameter() patch (MAX_TEXTURE_SIZE и др.) | getParameter() patch |
| Do Not Track | navigator.doNotTrack override | navigator.doNotTrack override |
| Global Privacy Control | navigator.globalPrivacyControl override | navigator.globalPrivacyControl override |
| Intl locale spoofing | Intl.* конструкторы → spoofed locale | Intl.* конструкторы → spoofed locale |
| Screen orientation | screen.orientation override | screen.orientation override |
| matchMedia | prefers-color-scheme / prefers-reduced-motion | prefers-color-scheme / prefers-reduced-motion |
| Timer precision | performance.now() / Date.now() rounding | performance.now() / Date.now() rounding |
| WebSocket protection | block / same-origin | block / same-origin |
| WebGL readPixels noise | readPixels() шум (WebGL + WebGL2) | readPixels() шум (WebGL + WebGL2) |
| Storage quota | navigator.storage.estimate() fake | navigator.storage.estimate() fake |
| Keyboard layout | keyboard.getLayoutMap() QWERTY override | keyboard.getLayoutMap() QWERTY override |
| WebRTC ICE rewrite | sanitize private IPs / block candidates | sanitize private IPs / block candidates |
| Plugin/MimeType | navigator.plugins / mimeTypes fake | navigator.plugins / mimeTypes fake |
| Speech Recognition | SpeechRecognition заглушка | SpeechRecognition заглушка |
| Touch/maxTouchPoints | navigator.maxTouchPoints override | navigator.maxTouchPoints override |
| AudioContext params | sampleRate + maxChannelCount override | sampleRate + maxChannelCount override |
| PDF Viewer | navigator.pdfViewerEnabled override | navigator.pdfViewerEnabled override |
| Notification | Notification.permission override | Notification.permission override |
| Gamepad | getGamepads() → [], блокировка событий | getGamepads() → [], блокировка событий |
| Hardware API | Скрытие bluetooth/usb/serial/hid | Скрытие bluetooth/usb/serial/hid |
| Performance | getEntries() → [], PerformanceObserver фильтр | getEntries() → [], PerformanceObserver фильтр |
| Referrer | document.referrer override | document.referrer override |
| History | history.length override | history.length override |
| DeviceMotion | Блокировка DeviceOrientation/DeviceMotion | Блокировка DeviceOrientation/DeviceMotion |
| AmbientLight | Блокировка AmbientLightSensor | Блокировка AmbientLightSensor |
| Connection RTT/downlink | Standalone rtt/downlink override | Standalone rtt/downlink override |
| MediaCapabilities | decodingInfo → always supported | decodingInfo → always supported |
| Clipboard | Блокировка read/readText | Блокировка read/readText |
| Web Share | Блокировка share/canShare | Блокировка share/canShare |
| Wake Lock | Блокировка wakeLock.request | Блокировка wakeLock.request |
| Idle Detection | Блокировка IdleDetector | Блокировка IdleDetector |
| Credential Mgmt | Блокировка credentials.get/create | Блокировка credentials.get/create |
| Payment Request | Блокировка PaymentRequest | Блокировка PaymentRequest |
| Storage Estimate | storage.estimate() usage override | storage.estimate() usage override |
| File System Access | Блокировка File Picker API | Блокировка File Picker API |
| Beacon | sendBeacon → silent true | sendBeacon → silent true |
| Visibility State | visibilityState override + event block | visibilityState override + event block |
| Color Depth | colorDepth/pixelDepth override | colorDepth/pixelDepth override |
| Installed Apps | Блокировка getInstalledRelatedApps | Блокировка getInstalledRelatedApps |
| Font Metrics | Нормализация метрик шрифтов | Нормализация метрик шрифтов |
| Cross-Origin Isolation | crossOriginIsolated override + SharedArrayBuffer блокировка | crossOriginIsolated override + SharedArrayBuffer блокировка |
| Performance.now Jitter | Рандомный jitter performance.now() | Рандомный jitter performance.now() |
| Window Controls Overlay | Скрытие windowControlsOverlay API | Скрытие windowControlsOverlay API |
| Screen Orientation Lock | Блокировка screen.orientation.lock() | Блокировка screen.orientation.lock() |
| Keyboard API | Блокировка keyboard.getLayoutMap() | Блокировка keyboard.getLayoutMap() |
| USB/HID/Serial | Скрытие navigator.usb/hid/serial | Скрытие navigator.usb/hid/serial |
| Presentation API | Скрытие navigator.presentation | Скрытие navigator.presentation |
| Contacts API | Скрытие navigator.contacts | Скрытие navigator.contacts |
| Bluetooth | Скрытие navigator.bluetooth | Скрытие navigator.bluetooth |
| Eye Dropper | Блокировка EyeDropper API | Блокировка EyeDropper API |
| Multi-Screen | Блокировка getScreenDetails() | Блокировка getScreenDetails() |
| Ink API | Скрытие navigator.ink | Скрытие navigator.ink |
| Virtual Keyboard | Скрытие navigator.virtualKeyboard | Скрытие navigator.virtualKeyboard |
| Web NFC | Скрытие NDEFReader | Скрытие NDEFReader |
| File Handling | Блокировка launchQueue | Блокировка launchQueue |
| WebXR | Скрытие navigator.xr | Скрытие navigator.xr |
| Web Neural Network | Блокировка navigator.ml | Блокировка navigator.ml |
| Scheduling | Скрытие scheduler.postTask() / yield() | Скрытие scheduler.postTask() / yield() |
| Storage Access | Блокировка requestStorageAccess() | Блокировка requestStorageAccess() |
| Content Index | Блокировка registration.index | Блокировка registration.index |
| Background Sync | Блокировка sync / periodicSync | Блокировка sync / periodicSync |
| Cookie Store | Скрытие window.cookieStore | Скрытие window.cookieStore |
| Web Locks | Скрытие navigator.locks | Скрытие navigator.locks |
| Shape Detection | Скрытие BarcodeDetector / FaceDetector / TextDetector | Скрытие BarcodeDetector / FaceDetector / TextDetector |
| Web Transport | Блокировка WebTransport | Блокировка WebTransport |
| Related Apps | Блокировка getInstalledRelatedApps() | Блокировка getInstalledRelatedApps() |
| Digital Goods | Скрытие getDigitalGoodsService() | Скрытие getDigitalGoodsService() |
| Compute Pressure | Скрытие PressureObserver | Скрытие PressureObserver |
| File System Picker | Блокировка showDirectoryPicker() | Блокировка showDirectoryPicker() |
| Display Override | Скрытие windowControlsOverlay | Скрытие windowControlsOverlay |
| Battery Level Override | Подмена BatteryManager.level | Подмена BatteryManager.level |
| Picture-in-Picture | Блокировка PiP API | Блокировка PiP API |
| Device Posture | Скрытие navigator.devicePosture | Скрытие navigator.devicePosture |
| WebAuthn | Блокировка FIDO credentials | Блокировка FIDO credentials |
| FedCM | Блокировка identity credentials | Блокировка identity credentials |
| Local Font Access | Блокировка queryLocalFonts() | Блокировка queryLocalFonts() |
| Autoplay Policy | Блокировка getAutoplayPolicy() | Блокировка getAutoplayPolicy() |
| Launch Handler | Скрытие LaunchParams | Скрытие LaunchParams |
| Topics API | Блокировка browsingTopics() | Блокировка browsingTopics() |
| Attribution Reporting | Блокировка attributionSrc | Блокировка attributionSrc |
| Fenced Frames | Скрытие HTMLFencedFrameElement | Скрытие HTMLFencedFrameElement |
| Shared Storage | Блокировка sharedStorage | Блокировка sharedStorage |
| Private Aggregation | Блокировка privateAggregation | Блокировка privateAggregation |
| Web OTP | Блокировка OTPCredential | Блокировка OTPCredential |
| Web MIDI | Блокировка requestMIDIAccess() | Блокировка requestMIDIAccess() |
| WebCodecs | Блокировка VideoEncoder/AudioEncoder | Блокировка VideoEncoder/AudioEncoder |
| Navigation API | Скрытие window.navigation | Скрытие window.navigation |
| Screen Capture | Блокировка getDisplayMedia() | Блокировка getDisplayMedia() |
| Proxy | chrome.proxy.settings (глобальный, переключается по активной вкладке) | proxy.onRequest (per-tab routing) |
| navigator.webdriver | false | false |
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Escorp.Atom.Media.Audio (>= 0.0.1)
- Escorp.Atom.Media.Video (>= 0.4.1)
- Escorp.Atom.Net (>= 0.0.6)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.