Минимальный доступ и параметры адреса
Расширение уже сохраняет выбранную страницу и ищет собственные записи. Теперь рассмотрим границу доступа и состав сохраняемого адреса. Для заметки не всегда нужны параметры URL: в них могут находиться пользовательские фильтры или сведения сессии. Результат главы — явное объяснение трёх разрешений и настройка, по умолчанию удаляющая query у новых SAVE. Это не автоматическая очистка всей истории и не распознавание всех возможных секретов.
Разрешение отвечает за возможность вызвать API, а приватность записи — за данные, которые приложение сохраняет после разрешённого действия. Оба вопроса важны и не заменяют друг друга. ActiveTab уже ограничивает чтение пользовательским вызовом; однако даже законно полученный URL может содержать лишние сведения. Поэтому полезно посмотреть на путь данных после executeScript, а не остановиться на коротком списке manifest.
Зачем каждое разрешение
activeTab даёт временное основание доступа к текущей вкладке после действия пользователя. scripting позволяет выполнить наш content.js. storage сохраняет локальные заметки и настройки. Пакет не имеет постоянных host_permissions и не запрашивает all_urls. Он не читает историю всех переходов, не наблюдает страницы непрерывно и не загружает удалённый код. Эти свойства следуют из конкретного manifest и обработчиков, а не из общего обещания «расширение уважает приватность».
Если убрать scripting, activeTab сам по себе не исполнит content.js. Если убрать storage, чтение страницы ещё возможно, но постоянная запись перестанет иметь нужное API. Если убрать activeTab и не назначить другой подходящий host-договор, выполнение на чужой странице не получит основание. Такое сравнение помогает понять отдельные обязанности разрешений. Не добавляем tabs только ради query текущей вкладки: доступ к нужным полям связан с выбранным пользовательским действием и платформенным договором.
При служебной странице ожидается отказ, несмотря на заявленные разрешения. Manifest не отменяет ограничения браузера. Расширение должно показать человеку, что выбранная поверхность недоступна, и позволить использовать обычную собственную веб-страницу. Мы не создаём workaround для chrome:// и не пытаемся извлечь сведения браузерного аккаунта. Учебный проект сохраняет ограниченный снимок документа по действию, а не исследует все возможности чтения системы.
Полные файлы шага
Показанные файлы полностью совпадают с выбранным состоянием архива. Остальные согласованные файлы находятся в examples/browser-extension-lab/lesson-11/.
PERMISSIONS.md
# Доступ к выбранной странице
Профиль: Chrome Manifest V3, minimum_chrome_version 102.
activeTab выдаёт временный доступ после открытия action пользователем.
scripting исполняет только наш content.js, storage сохраняет локальные заметки.
Нет постоянных host_permissions, внешнего кода, сети, telemetry или all_urls.
Нет onMessageExternal и моста window.postMessage к privileged API.
Не сохраняйте секреты; очищение query не распознаёт секрет в пути/цитате.
Старые записи и импорт не меняются автоматически новой настройкой.
Разрешение в manifest не гарантирует доступ к chrome:// и другим служебным страницам.
options.js
const status = document.querySelector("#status");
const defaultInput = document.querySelector("#default");
async function request(type, payload) {
const reply = await chrome.runtime.sendMessage({ version: 1, type, payload });
if (!reply?.ok) throw new Error(reply?.error ?? "Нет ответа");
return reply.data;
}
request("GET_SETTINGS").then((settings) => {
defaultInput.value = settings.defaultNote;
document.querySelector("#full-url").checked = settings.storeFullUrl;
}).catch((error) => { status.textContent = error.message; });
document.querySelector("#apply").addEventListener("click", async () => {
try {
await request("SET_SETTINGS", { defaultNote: defaultInput.value,
storeFullUrl: document.querySelector("#full-url").checked
});
status.textContent = "Настройки сохранены";
} catch (error) { status.textContent = error.message; }
});
document.querySelector("#export").addEventListener("click", async () => {
try {
const notes = await request("LIST");
const body = JSON.stringify({ format: "reader-notes", version: 1, notes }, null, 2);
const url = URL.createObjectURL(new Blob([body], { type: "application/json" }));
const link = document.createElement("a");
link.href = url;
link.download = "reader-notes.json";
document.body.append(link);
link.click();
link.remove();
setTimeout(() => URL.revokeObjectURL(url), 1000);
status.textContent = "Файл экспорта подготовлен браузером";
} catch (error) { status.textContent = error.message; }
});
document.querySelector("#import").addEventListener("click", async () => {
try {
const file = document.querySelector("#file").files[0];
if (!file) throw new Error("Выберите свой файл JSON");
if (file.size > 512 * 1024) throw new Error("Файл больше 512 КиБ");
const bundle = JSON.parse(await file.text());
const preview = document.querySelector("#preview");
preview.textContent = `В файле ${Array.isArray(bundle.notes) ? bundle.notes.length : 0} записей`;
if (!confirm("Добавить отсутствующие заметки? Существующие записи сохранятся.")) return;
const result = await request("IMPORT", bundle);
status.textContent = `Добавлено: ${result.added}; пропущено: ${result.skipped}`;
} catch (error) { status.textContent = `Импорт не завершён: ${error.message}`; }
});
Ожидаемый результат
Это ожидаемый договор для самостоятельного исследования; расширение не устанавливалось и не запускалось.
Новый SAVE, storeFullUrl=false → URL без query/hash.
Новый SAVE, storeFullUrl=true → query остаётся, hash удалён.
Прежние и импортированные заметки не переписываются.
Служебные страницы могут отказать при любых заявленных permissions.
Query — часть данных
validateNote уже удаляет hash и запрещает URL с username/password. Теперь ветка SAVE читает settings и, если storeFullUrl не true, создаёт URL, очищает search и сохраняет изменённый href. Адрес example.invalid/read?topic=range станет example.invalid/read. Мы используем вымышленный домен для объяснения, не выполняя сетевой запрос. Очистка касается параметров после вопросительного знака и не меняет путь страницы.
Это может повлиять на полезность ссылки. Некоторые сайты используют query для выбора конкретной статьи или состояния поиска. Поэтому options показывает явный checkbox «Сохранять параметры адреса» и поясняет риск. По умолчанию он выключен. Пользователь выбирает, нужен ли полный адрес для его собственной задачи, а расширение применяет выбор к новым записям. Такой подход честно показывает tradeoff вместо обещания, что одна нормализация всегда сохранит все смыслы URL.
Если секрет находится в пути, заголовке, цитате или собственном note, удаление query его не обнаружит. Нельзя называть эту настройку полной обезличивающей очисткой. Мы не разрабатываем анализатор чувствительных данных и не скрываем предел нормализации. Пользователь не должен сохранять секреты в проекте; при этом обучение не требует сообщения о гипотетической опасности каждой строки. Достаточно точного объяснения того, какое преобразование действительно выполняет код и какие поля остаются содержанием читателя.
Прежние записи имеют прежний смысл
Новая настройка не переписывает уже сохранённые заметки. Это осознанное правило: массовое изменение URL могло бы разрушить полезные ссылки без отдельного выбора пользователя. Импорт также сохраняет проверенные адреса документа, а не подгоняет всю историю под текущий checkbox. Если нужна очистка старой коллекции, её следует сделать самостоятельной операцией с предварительным просмотром и ясным результатом. Текущий урок относится к новым намерениям SAVE и не объявляет такую миграцию выполненной.
Options читает storeFullUrl из нормализованных settings и сохраняет boolean вместе с defaultNote. Страница не получает возможность назначать произвольное имя настройке или передать функцию обработки URL. Worker принимает только известную форму. Content script по-прежнему возвращает данные основного документа и не имеет универсальной команды privileged API. Отсутствие onMessageExternal и моста window.postMessage также сохраняется: страница не приобретает право менять настройки по своему сценарию.
Ожидаемый положительный сценарий: при выключенном checkbox новая заметка получает URL без query и hash; при включённом query сохраняется, hash всё равно удаляется прежним валидатором. Импортированная или старая запись остаётся без изменений. Доступ к самой вкладке остаётся временным и обусловленным action. Автор эти сценарии не выполнял, историю пользователя не читал и не собирал реальные URL с параметрами аккаунта. В статье используются только учебные примеры адресов.
Доступ к storage тоже ограничен
TRUSTED_CONTEXTS оставляет local собственным страницам и worker. Content script не должен получать всю библиотеку заметок только ради чтения выделенной фразы. Ему достаточно вернуть текущий снимок. Это пример минимальности внутри расширения, а не только в manifest. Рабочая роль контекста определяется нужным действием: reader читает документ, интерфейс собирает намерение, worker проверяет и сохраняет. Возможность видеть одно поле не должна автоматически предоставлять всю коллекцию.
Полные PERMISSIONS.md и options.js на этой странице совпадают с lesson-11. Manifest остаётся с тремя обоснованными разрешениями и без постоянных host-доступов. Для самостоятельного разбора назовите две разные ситуации: API отказал читать страницу и URL успешно прочитан, но нормализован перед сохранением. Первое относится к полномочию, второе — к договору данных. Последняя глава соберёт пакет, версии и границы совместимости, чтобы локальная передача не выглядела как уже выполненная публикация магазина.
Параметры URL могут быть полезными и без чувствительных сведений. Поэтому настройка не запрещает их навсегда, а задаёт осторожное начальное поведение и осознанный выбор. Код не пытается определить значение имени token или прочитать содержание аккаунта; он выполняет один прозрачный шаг: очищает search у нового объекта перед set. Такой механизм легко проверить чтением файла и объяснить пользователю. Обещание более умного распознавания без соответствующего алгоритма только сделало бы договор менее точным и могло бы создать ложное доверие к сохранённому тексту.
Документация и исходники
Механизмы главы сверены с первичными источниками: документация 1, документация 2, документация 3. Полный архив «Заметок читателя» содержит все двенадцать состояний. Установка, запуск и публикация не выполнялись.