Перейти к содержанию

Popup: ввод заметки и безопасный вывод

Оглавление серии

Manifest открывает локальную страницу расширения. Добавим поле заметки, кнопку и строку состояния. Результат этой главы намеренно ограничен интерфейсом: введённый текст появляется как черновик, но ещё не сохраняется. Так мы увидим, какие данные принадлежат открытому DOM и почему закрытие popup нельзя считать надёжным способом хранения пользовательской работы.

Popup похож на обычную страницу по доступным DOM-операциям, однако живёт иначе. Пользователь открывает небольшой интерфейс для короткого действия, затем возвращается к вкладке. После закрытия обработчики и значения полей текущего экземпляра исчезают. Поэтому задача разработчика — быстро выполнить понятную операцию и показать её состояние. Долгую процедуру импорта позже вынесем в options, где закрытие краткого окна меньше мешает чтению и подтверждению.

Поле и событие

textarea получает id note, maxlength 2000 и доступное название aria-label. Ограничение в интерфейсе помогает пользователю, но не станет единственной проверкой данных. Позже worker повторно проверит длину, поскольку сообщение можно сформировать иным способом. Такое дублирование имеет разные обязанности: поле ограничивает удобный ввод, а принимающий контекст защищает договор операции. Не следует считать DOM-атрибут гарантией для всех сообщений расширения.

Кнопка имеет type="button", поэтому её смысл не зависит от случайной формы-родителя. Мы находим её через querySelector и подписываем обработчик click через addEventListener. Код подключён внешним popup.js, без inline-обработчика в HTML. Это позволяет читать структуру интерфейса отдельно от поведения и не смешивать пользовательскую строку с исполняемым содержимым. В выбранном пакете все выполняемые файлы локальны и входят в снимок.

Обработчик берёт value из textarea. Это текст, который пользователь написал сейчас. Он ещё не является объектом заметки с номером, датой и адресом. Поэтому надпись начинается со слова «Черновик». Если значение пустое, выводим предложение ввести текст. Мы не сообщаем «Сохранено», потому что storage в этой ступени не подключён. Точность сообщения состояния важна: интерфейс не должен обещать факт, которого код не совершил.

Полные файлы шага

Показанные файлы полностью совпадают с выбранным состоянием архива. Остальные согласованные файлы находятся в examples/browser-extension-lab/lesson-02/.

popup.html

<!doctype html>
<html lang="ru">
<head>
  <meta charset="utf-8">
  <title>Заметки читателя</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <h1>Заметки читателя</h1>
  <p>Сохраняйте собственную заметку о выбранной странице.</p>
  <textarea id="note" maxlength="2000" aria-label="Ваша заметка"></textarea>
  <button id="save" type="button">Сохранить страницу</button>
  <button id="settings" type="button">Настройки</button>
  
  <p id="status" role="status" aria-live="polite"></p>
  <ul id="notes"></ul>
  <script type="module" src="popup.js"></script>
</body>
</html>

popup.js

const status = document.querySelector("#status");
document.querySelector("#save").addEventListener("click", () => {
  const value = document.querySelector("#note").value;
  status.textContent = value ? `Черновик: ${value}` : "Введите свою заметку";
});
document.querySelector("#settings").hidden = true;

style.css

body {
  font-family: system-ui, sans-serif;
  margin: 1rem;
  min-width: 300px;
  max-width: 700px;
}
button, input, textarea { font: inherit; }
textarea, input[type="search"] { display: block; width: 95%; margin: .5rem 0; }
button { margin: .3rem .3rem .3rem 0; }
li { margin: .7rem 0; overflow-wrap: anywhere; }
#status { min-height: 1.5em; }

Ожидаемый результат

Это ожидаемый договор для самостоятельного исследования; расширение не устанавливалось и не запускалось.

Ввод собственной строки → «Черновик: …»
Пустое поле → «Введите свою заметку»
Текст <b> остаётся буквальным текстом.
Закрытие popup уничтожает этот несохранённый черновик.

TextContent и чужой текст

Для вывода используется textContent. Строка с символами должна остаться строкой, а не стать жирным HTML-элементом. Этот выбор станет особенно важным после чтения заголовка чужого сайта и импорта JSON. Чужой текст может содержать знаки разметки, но расширение отображает содержание, а не исполняет полученный HTML. Не выбираем innerHTML ради удобного объединения строки: в нашем проекте нет задачи разрешать произвольную разметку заметок.

Строка состояния имеет role="status" и aria-live="polite". Это помогает передать изменившийся текст без необходимости искать его визуально после каждого нажатия. Мы не гарантируем одинаковый звук или время объявления во всех вспомогательных средствах: это зависит от среды читателя. Однако семантическая разметка задаёт понятную цель элемента. В уроке не нужен отдельный всплывающий alert для обычного черновика, который уже находится рядом с полем.

HTML также содержит будущий список notes и кнопку settings, но JavaScript пока скрывает настройки. Эти элементы подготовлены как часть последовательного интерфейса; они ещё не обещают работающую страницу options. Скрытая кнопка не используется для защиты privileged API, поскольку доступа к этим операциям сейчас нет. Позже worker получит настоящую проверку отправителя и команд. Внешний вид кнопки и право выполнить действие необходимо рассматривать отдельно.

Короткое состояние интерфейса

В style.css задаём системный шрифт, отступы и ширину полей. Размеры просты, потому что главный результат урока связан с событием и текстом. Не создаём собственную библиотеку компонентов для одной операции. Кнопки и textarea сохраняют нативную семантику, а строки могут переноситься. Это важно для длинной заметки: текст не должен вынудить popup расширяться без понятной границы. Постоянный список появится позднее, но основные правила вывода останутся теми же.

Ожидаемый положительный сценарий: человек вводит «Сравнить два объяснения диапазонов» и нажимает кнопку. Строка состояния показывает этот текст после «Черновик». Если закрыть popup и открыть снова, поле окажется в новом исходном состоянии. Это не потеря уже сохранённой записи, потому что запись не производилась. Такой опыт читатель может провести самостоятельно; автор расширение не устанавливал и не наблюдал этот экран в браузере.

Проверочный ошибочный вариант — ввести текст, похожий на HTML. Он должен отображаться буквально. Ещё один вариант — нажать кнопку с пустым полем: ожидается просьба ввода. Мы не добавляем универсальную ловушку всех исключений, поскольку текущая операция синхронна и имеет два определённых исхода. Когда появятся асинхронные API браузера, понадобятся try/catch и явное ожидание завершения, иначе обещание сохранения появится слишком рано.

Ввод и факт остаются разными

Представьте, что пользователь пишет заметку, но ещё не нажал кнопку. Текст находится только в DOM. Даже после нажатия в этой версии он переходит лишь в строку состояния того же окна. Обе области исчезают вместе с popup. Настоящее сохранение будет иметь третий этап: проверенный объект передаётся worker, storage подтверждает запись, интерфейс получает ответ и только затем показывает успех. Сейчас полезно заранее различить эти границы, чтобы позднее не перепутать сообщение отправки с завершённым фактом.

Полные HTML, JavaScript и CSS на этой странице совпадают со снимком lesson-02. При переносе берите весь каталог, потому что старый HTML не подключает popup.js и не содержит нужные id. Результат главы — управляемый ввод и безопасный вывод текста. Следующий урок вынесет обработку событий в service worker и покажет, почему общий фон Manifest V3 не должен хранить заметки в глобальном массиве, даже если такой массив кажется удобным при первом чтении.

Вывод строки и изменение поля имеют разные роли. TextContent назначается элементу статуса, а value относится к редактируемому textarea. Если вместо value менять текстовый узел внутри textarea после пользовательского ввода, легко получить непонятное расхождение исходного содержимого и текущего значения. В нашем обработчике источник читается через value, а результат выводится отдельно. Так пользователь продолжает видеть свой черновик и не теряет его из-за попытки превратить поле ввода одновременно в журнал сообщений программы.

Документация и исходники

Механизмы главы сверены с первичными источниками: документация 1, документация 2, документация 3. Полный архив «Заметок читателя» содержит все двенадцать состояний. Установка, запуск и публикация не выполнялись.