fix: bundle the content script to stop 'Unsafe attempt to load URL' errors

Dynamic import() of extension modules from a content script makes Chrome log
'Unsafe attempt to load URL chrome-extension://...' on YouTube pages, which fills
the extension's error list even though the modules load (HTTP 200). The content
script is now one classic file built by 'npm run build'; web_accessible_resources
is no longer needed and is removed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
IgorVolochay
2026-10-25 12:25:06 +03:00
co-authored by Claude Sonnet 5.5
parent 38dabd9fa2
commit b137776381
9 changed files with 461 additions and 61 deletions
@@ -1594,3 +1594,21 @@ Expected: все проверки проходят.
git add README.md
git commit -m "docs: describe the VidIQ-based workflow" -m "Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>"
```
---
### Task 10 (добавлена после проверки в браузере): один обычный файл для content script
**Почему:** в `chrome://extensions` у расширения копились ошибки «Unsafe attempt to load URL chrome-extension://…/src/lib/video-id.js from frame with URL https://www.youtube.com/…». Разбор показал: Chrome пишет это сообщение при динамическом `import()` модулей из content script (недетерминированно, воспроизводится даже на минимальном примере из двух файлов), хотя запросы завершаются статусом 200 и расширение работает. Чтобы не копить ошибки, content script собирается в один обычный файл без `import()`.
**Files:**
- Create: `tools/build-content.js` (функция `bundle(entry)` и запуск `npm run build`), `src/content/main.js` (исходник content script на статических `import`), `test/bundle.test.js`
- Generate: `src/content.js` (`npm run build`, коммитится)
- Modify: `manifest.json` (убрать `web_accessible_resources`), `package.json` (скрипт `build`), `README.md`, спецификация
**Что проверено:**
- [x] `test/bundle.test.js` (6 тестов): порядок модулей, общий модуль включён один раз, отказ при совпадающих именах и неподдерживаемом `export`, закоммиченный `src/content.js` совпадает со свежей сборкой, в нём нет `import()`, в манифесте нет `web_accessible_resources`.
- [x] Три прогона воспроизведения на главной, поиске, просмотре и канале: сообщений «Unsafe attempt» нет (раньше не меньше четырёх на страницу).
- [x] Сквозная проверка в Chrome (26 проверок) проходит на собранном файле.
**Правило разработки:** после правки `src/content/main.js` или модулей из `src/lib/`, которые он использует, выполните `npm run build`; без этого тест `bundle.test.js` падает.
@@ -18,7 +18,7 @@
| Хранение | IndexedDB в браузере, экспорт в JSON и CSV из popup |
| Страницы | Главная, страница просмотра (боковые карточки), поиск, страница канала |
| Языки интерфейса | Русский и английский |
| Сборка | Нет. Чистый JS, ES-модули, загрузка как «распакованное расширение» |
| Сборка | Только для content script: `npm run build` склеивает `src/content/main.js` и его модули в один обычный файл `src/content.js` (он закоммичен, тест следит, чтобы он не отставал). Причина: динамический `import()` в content script оставляет в `chrome://extensions` ошибки «Unsafe attempt to load URL chrome-extension://…», хотя модули грузятся. Остальной код без сборки: чистый JS, ES-модули, загрузка как «распакованное расширение» |
## Откуда берётся каждое поле
@@ -38,7 +38,8 @@
| Часть | Файл | Ответственность |
|---|---|---|
| Content script | `src/content.js` | Наблюдает за DOM на `youtube.com`, разбирает карточки, отправляет в background только карточки, где VidIQ уже вывел score, и только когда данные изменились |
| Content script | `src/content/main.js` → `src/content.js` | Наблюдает за DOM на `youtube.com`, разбирает карточки, отправляет в background только карточки, где VidIQ уже вывел score, и только когда данные изменились. В манифесте подключён собранный `src/content.js`, исходник — `src/content/main.js` |
| Сборка content script | `tools/build-content.js` | Склеивает исходник и импортируемые модули в один обычный скрипт (поддерживает только однострочные `import` и `export` перед `const/let/function/class`, имена верхнего уровня должны быть уникальны) |
| Service worker | `src/background.js` | Принимает карточки, подтягивает настройки и ранее сохранённые записи, пишет результат в IndexedDB. Записи обрабатываются строго по очереди |
| Разбор карточки | `src/lib/card.js` | `scrapeCard(card, link, context)`: достаёт поля из DOM-элемента карточки |
| Данные VidIQ | `src/lib/vidiq.js` | Селекторы VidIQ, `outlierScoreFromHref`, `parseVidiqScore`, `readVidiqData` |
@@ -101,6 +102,7 @@ CSV содержит эти же поля в том же порядке.
## Тестирование
- **Автотесты** на встроенном `node:test`, без зависимостей: `numbers.js`, `vidiq.js` (на подставных объектах), `record.js`, `video-id.js`, `export.js`, `storage.js`.
- **Тест сборки** (`test/bundle.test.js`): сборщик на временных файлах, проверка, что закоммиченный `src/content.js` совпадает со свежей сборкой, что в нём нет `import()` и что в манифесте нет `web_accessible_resources`.
- **Тест разбора карточки в Chrome** (`test/card.dom.test.js`): запускает headless Chrome, загружает фикстуры и проверяет `scrapeCard`. Фикстуры: настоящая карточка с VidIQ с главной, плюс упрощённые карточки поиска и боковой панели. Если Chrome не найден, тест пропускается.
- **Сквозная проверка в Chrome** (не входит в репозиторий): живой YouTube с подмешанным в карточки блоком VidIQ, проход через content script, background, IndexedDB и popup.