Реалізація Popup-інтерфейсу браузерного розширення
Popup — це HTML-сторінка, яка відкривається при кліку на іконку розширення в тулбарі. Вона живе від натиснення до закриття (втрата фокусу або явне закриття) та не зберігає стан між відкриттями — це потрібно врахувати при проектуванні.
Обмеження Popup
- Максимальний розмір: ~800×600 px, Chrome обмежує висоту viewport
- Життєвий цикл: створюється при відкритті, знищується при закритті
- Немає
alert(),confirm()— заблоковано браузером - CSP забороняє
inline scriptsтаeval— весь JS повинен бути у.jsфайлах - Неможна відкривати програмно з content script без user gesture (у MV3)
Структура файлів
popup/
├── popup.html
├── popup.js (або popup.tsx + збирання)
└── popup.css
<!-- popup.html -->
<!DOCTYPE html>
<html lang="uk">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=350">
<link rel="stylesheet" href="popup.css">
<!-- НЕМАЄ inline scripts — порушення CSP -->
</head>
<body>
<div id="app"></div>
<script src="popup.js"></script>
</body>
</html>
React у Popup
Popup з React — стандартна практика для складних інтерфейсів:
// popup/App.tsx
import { useEffect, useState } from 'react';
import browser from 'webextension-polyfill';
interface TabInfo {
url: string;
title: string;
}
export function App() {
const [tab, setTab] = useState<TabInfo | null>(null);
const [enabled, setEnabled] = useState(false);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function init() {
// Отримати дані активної вкладки
const [activeTab] = await browser.tabs.query({ active: true, currentWindow: true });
setTab({ url: activeTab.url ?? '', title: activeTab.title ?? '' });
// Завантажити збережені налаштування
const { extensionEnabled } = await browser.storage.local.get('extensionEnabled');
setEnabled(extensionEnabled ?? false);
setLoading(false);
}
init();
}, []);
async function toggleEnabled() {
const next = !enabled;
setEnabled(next);
await browser.storage.local.set({ extensionEnabled: next });
// Сповістити content script на активній вкладці
if (tab) {
const [activeTab] = await browser.tabs.query({ active: true, currentWindow: true });
if (activeTab.id) {
browser.tabs.sendMessage(activeTab.id, { type: 'TOGGLE', enabled: next });
}
}
}
if (loading) return <div className="popup-loading">Завантаження...</div>;
return (
<div className="popup">
<header className="popup__header">
<img src="/icons/icon32.png" alt="" />
<h1>My Extension</h1>
</header>
<section className="popup__body">
<div className="popup__url" title={tab?.url}>{tab?.title}</div>
<label className="toggle">
<input
type="checkbox"
checked={enabled}
onChange={toggleEnabled}
/>
<span className="toggle__label">
{enabled ? 'Включено' : 'Вимкнено'}
</span>
</label>
</section>
<footer className="popup__footer">
<button onClick={() => browser.runtime.openOptionsPage()}>
Налаштування
</button>
<button onClick={() => browser.tabs.create({ url: 'https://example.com/help' })}>
Допомога
</button>
</footer>
</div>
);
}
Розмір та стилізація
Popup повинен бути зручним у діапазоні 320–400 px ширини. Типова верстка:
/* popup.css */
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
width: 360px;
min-height: 200px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-size: 14px;
color: #1a1a1a;
background: #fff;
}
.popup {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.popup__header {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 16px;
border-bottom: 1px solid #e5e7eb;
}
.popup__body {
flex: 1;
padding: 16px;
}
.popup__footer {
display: flex;
gap: 8px;
padding: 12px 16px;
border-top: 1px solid #e5e7eb;
}
Терміни
Простий popup з переключателем та сховищем: 1–2 дні. Складний popup з React, фоновою синхронізацією та кількома вкладками: 3–5 днів.







