🔌 Руководство по разработке адаптеров
Адаптеры ForcedSkin пишутся как декларативные JSON-формулы ( forcedskin-adapter-formula/v1 ), описывающие целевые hostname и слои селекторов для окраски. Сервер хранит только JSON; расширение поставляет фиксированный интерпретатор , который применяет правила покраски — никогда произвольный JavaScript.
Как это работает
Базовый движок начинает с наложений CSS-переменных и inline-стилей для исходной перекраски.
Когда hostname совпадает, адаптеры выполняются по возрастанию приоритета — чем меньше число, тем раньше запускается интерпретатор (рекомендуемые адаптеры сайтов используют 100).
Каждый адаптер подстраивает целевые узлы через открытые helpers engineApi; пропускайте прозрачные оверлеи / стеки плеера согласно рекомендациям.
Среда выполнения уже отслеживает изменения DOM и повторно применяет адаптеры с троттлингом — MutationObserver писать не нужно.
Формулы приходят из GET /api/pub/extension-adapters ; JSON каждого записи кэшируется локально. Обновление копии на портале вступает в силу, когда пользователи обновят адаптеры или снова откроют браузер — переустановка не нужна.
Минимальный пример
{
"schema": "forcedskin-adapter-formula/v1",
"id": "example-site",
"priority": 100,
"match": {
"hostname": [
{ "op": "suffixDomain", "value": "example.com" }
]
},
"layers": [
{
"kind": "surface",
"skipOverlayLike": true,
"selectors": [".site-navbar", ".site-header"]
}
]
}Формула адаптера forcedskin-adapter-formula/v1
Отправленный JSON должен пройти серверную валидацию — корневая схема выглядит так:
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
| schema | string | Обязательно | Литеральная строка версии forcedskin-adapter-formula/v1 |
| id | string | Обязательно | Логический id адаптера — обычно кодовое имя сайта, напр. bilibili |
| priority | number | Необязательно | Порядок выполнения (по возрастанию). Адаптеры конкретных сайтов обычно используют 100 |
| match.hostname | Rule[] | Обязательно | Массив объектов правил hostname (см. таблицу ниже) |
| layers | Layer[] | Обязательно | Упорядоченные слои покраски — типы описаны в таблице слоёв |
Правила match.hostname
| Оператор | Значение |
|---|---|
| equals | Hostname равен value (без учёта регистра) |
| suffixDomain | Hostname равен value или оканчивается на .value (покрывает example.com и *.example.com) |
Ключи палитры (richText cssVars / color)
| Поле | Описание |
|---|---|
| background | Фон страницы |
| foreground | Основной текст |
| surface | Заливка карточки / панели |
| surfaceMuted | Приглушённые контейнеры / заливка hover |
| border | Цвет рамки |
| muted | Второстепенный текст |
| primary500 | Основной акцент (ссылки) из theme primary.500 |
| primary700 | Более глубокое состояние primary из primary.700 или 800 |
Рекомендуемые типы слоёв
Думайте семантически — не только по именам элементов. См. закоммиченный пример home/server/seeds/bilibili-adapter.formula.json для полного разбора.
| Тип слоя | Назначение | Типичное соответствие | markApplied |
|---|---|---|---|
| surface | Панели, строки списков, оболочки навигации | background→surface, color→foreground, border→border | bg + текст + рамка |
| accent | Активные вкладки / текущие строки списка | background+border→primary700, color→background для контраста | bg + текст + рамка |
| canvas | Hero-блоки с растровым фоном | Убрать background-image, background→palette background | Обычно только фон |
| richText | Сайты с фирменными web components | Выдать нужные CSS-переменные + текстовое сопоставление цвета | текст (иногда + фон) |
| svgRecolor | Встроенные SVG-глифы | fill/stroke по умолчанию→currentColor; необязательные ключи палитры fill/stroke для фиксированного цвета | Необязательная пометка, чтобы не мешать очистке |
Пропускайте рискованные области: Движок уже игнорирует media, canvas, iframe, тяжёлые blend/backdrop-слои и узлы, чьи классы намекают на маски или оверлеи — расширьте эти ограничения для полупрозрачного хрома плееров.
Отказ хоста: Пометьте поддеревья (напр. превью) с помощью data-gts-ignore , чтобы потомки пропускали глобальную перекраску.
Фрагмент полного примера
{
"schema": "forcedskin-adapter-formula/v1",
"id": "bilibili",
"priority": 100,
"match": {
"hostname": [
{ "op": "equals", "value": "bilibili.com" },
{ "op": "suffixDomain", "value": "bilibili.com" }
]
},
"layers": [
{
"kind": "surface",
"skipOverlayLike": true,
"selectors": ["[class*='bili-']", "[class*='bpx-']"]
},
{
"kind": "accent",
"selectors": ["[class*='active']", ".bili-dyn-list-tabs__item.active"]
},
{
"kind": "canvas",
"selectors": [".message-bg", ".message-bgc"]
},
{
"kind": "richText",
"selectors": ["bili-rich-text"],
"cssVars": {
"--bili-rich-text-color": "foreground",
"--bili-rich-text-link-color": "primary500",
"--bili-rich-text-link-color-hover": "primary700"
},
"color": "foreground"
},
{
"kind": "svgRecolor",
"selectors": ["svg path", "svg rect", "svg circle"]
}
]
}Рекомендации
JSON должен строго валидироваться
Некорректные схема / слои / правила хоста отклоняются при проверке — опирайтесь на минимальный пример и seeds этого руководства перед отправкой.
Включайте skipOverlayLike на surface
Совпадает с защитами ядра, чтобы полупрозрачные HUD / видеостеки не сплющивались в сплошную заливку.
Никогда не отправляйте JavaScript
Поле code — только JSON; расширение больше не вычисляет строки через new Function.
Чеклист отправки
🔐 Войдите перед отправкой заявки.
📋 Укажите отображаемое имя, домены через запятую и вставьте JSON в поле кода адаптера.
⏳ Сервер проверяет JSON-схему и правила безопасности; JavaScript и другой рискованный код отклоняются.
✅ Корректные формулы публикуются автоматически и доходят до пользователей расширения, которые синхронизируют адаптеры.
❌ Слишком широкие селекторы или некорректный JSON возвращают отзыв — исправьте и отправьте снова.
Формат целевого домена
siteDomain — человекочитаемый список; фактическое сопоставление идёт по match.hostname, например:
[
{ "op": "equals", "value": "bilibili.com" },
{ "op": "suffixDomain", "value": "bilibili.com" }
]Готовы? Откройте галерею адаптеров и отправьте JSON.
🔌 Отправить адаптер