Как создать расширение, закрывающее вкладки
Traffic Cardinal Traffic Cardinal  написал 22.09.2026

Как создать расширение, закрывающее вкладки

Traffic Cardinal Traffic Cardinal  написал 22.09.2026
10 мин
0
21
Содержание

Люди делятся на два типа: одни закрывают вкладки сразу же, другие — лишь тогда, когда браузер начнет безбожно лагать. Золотой середины, как вы понимаете, в этом мире нет. Мы решили исправить это недоразумение и сделали расширение, закрывающее неиспользуемые вкладки спустя указанный промежуток времени.

banner banner

Но сначала напомним, какие расширения мы уже делали:

Собственно, без лишних слов перейдем к делу!

Это важно

«Без лишних слов перейдем к делу» — хотелось бы нам написать, но есть нюансы, которые следует знать. И нюанса два. Во-первых, базовая инфа о создании расширений описана здесь. Мы специально опустили ее, чтобы не расписывать одно и то же из статьи в статью. Во-вторых, наше расширение будет состоять из следующих файлов:

  • manifest.json — в общем-то бесполезный, однако обязательный файл, он говорит браузеру о том, что это в принципе расширение.

  • background.js — в нем записана основная логика нашего расширения.

  • popup.html — html-форма менюшки, с помощью которой юзер управляет расширением.

  • popup.js — в нем записана логика ранее упомянутого меню.

Создаем расширение, закрывающее вкладки

Чтобы сделать расширение, достаточно создать папку, в которой буду четыре описанных ранее файла: а именно — manifest.json, background.js, popup.html, popup.js. После этого делаем следующие шаги:

1. Внутрь файла manifest.json вставляем следующий код:

{

"manifest_version": 3,

"name": "Tab Cleaner",

"version": "1.0.0",

"description": "Закрывает вкладки, которые давно не были активны.",

"permissions": [

"tabs",

"storage",

"alarms"

],

"background": {

"service_worker": "background.js"

},

"action": {

"default_popup": "popup.html"

},

"minimum_chrome_version": "88",

"incognito": "spanning",

"offline_enabled": true,

"short_name": "Tab Cleaner",

"author": "Tab Cleaner",

"homepage_url": "https://example.com/tab-cleaner",

"options_ui": {

"page": "popup.html",

"open_in_tab": false

}

}

2. Сохраняем файл manifest.json

3. Внутрь файла background.js вставляем следующий код:

const DEFAULT_VALUE = 7;

const DEFAULT_UNIT = "days";

const CHECK_INTERVAL_MINUTES = 1;


const EXTENSION_INFO = {

name: "Tab Cleaner",

version: "1.0.0",

mode: "automatic",

storageArea: "local",

trackingEnabled: true,

cleanupEnabled: true

};


const STORAGE_KEYS = {

thresholdValue: "thresholdValue",

thresholdUnit: "thresholdUnit",

excludedDomains: "excludedDomains",

lastActive: "lastActive"

};


const TIME_UNITS = {

minute: "minutes",

hour: "hours",

day: "days"

};


const CLEANUP_CONFIG = {

interval: CHECK_INTERVAL_MINUTES,

defaultValue: DEFAULT_VALUE,

defaultUnit: DEFAULT_UNIT,

preservePinned: true,

preserveActive: true

};


const DEFAULT_EXCLUDED_DOMAINS = [];

const EMPTY_TAB_LIST = [];

const EMPTY_DOMAIN_NAME = "";

const DEFAULT_TIMESTAMP = 0;

const STORAGE_VERSION = 1;

const SETTINGS_VERSION = 1;

const CLEANUP_VERSION = 1;

const EXTENSION_MODE = "standard";

const STORAGE_SCOPE = "extension";

const CLEANUP_REASON = "inactive";

const TAB_STATE_ACTIVE = "active";

const TAB_STATE_INACTIVE = "inactive";

const DOMAIN_SEPARATOR = ".";

const PROTOCOL_HTTP = "http:";

const PROTOCOL_HTTPS = "https:";

const URL_PREFIX_HTTP = "http://";

const URL_PREFIX_HTTPS = "https://";

const WWW_PREFIX = "www.";

const EMPTY_STRING = "";

const ZERO_VALUE = 0;

const ONE_VALUE = 1;


const RUNTIME_STATE = {

initialized: false,

lastCheck: DEFAULT_TIMESTAMP,

cleanupCount: ZERO_VALUE,

trackedTabs: ZERO_VALUE

};


async function getSettings() {

return await chrome.storage.local.get({

thresholdValue: DEFAULT_VALUE,

thresholdUnit: DEFAULT_UNIT,

excludedDomains: []

});

}


async function getLastActive() {

const data = await chrome.storage.local.get({

lastActive: {}

});

return data.lastActive;

}


async function setLastActive(lastActive) {

await chrome.storage.local.set({

lastActive

});

}


chrome.tabs.onActivated.addListener(async ({ tabId }) => {

const lastActive = await getLastActive();

lastActive[tabId] = Date.now();

await setLastActive(lastActive);

});


chrome.tabs.onCreated.addListener(async (tab) => {

if (tab.id == null) return;

const lastActive = await getLastActive();

lastActive[tab.id] = Date.now();

await setLastActive(lastActive);

});


chrome.tabs.onRemoved.addListener(async (tabId) => {

const lastActive = await getLastActive();

delete lastActive[tabId];

await setLastActive(lastActive);

});


chrome.alarms.create("checkTabs", {

periodInMinutes: CHECK_INTERVAL_MINUTES

});


chrome.alarms.onAlarm.addListener(async (alarm) => {

if (alarm.name === "checkTabs") {

await cleanTabs();

}

});


async function cleanTabs() {

const {

thresholdValue,

thresholdUnit,

excludedDomains

} = await getSettings();

const lastActive = await getLastActive();

const multipliers = {

minutes: 60 * 1000,

hours: 60 * 60 * 1000,

days: 24 * 60 * 60 * 1000

};


const maxAge =

thresholdValue * multipliers[thresholdUnit];

const now = Date.now();

const tabs = await chrome.tabs.query({});

const focusedWindow = await chrome.windows.getLastFocused();

const activeTabs = await chrome.tabs.query({

active: true,

windowId: focusedWindow.id

});

const activeTabIds = new Set(

activeTabs.map(tab => tab.id)

);


for (const tab of tabs) {

if (tab.id == null) {

continue;

}

if (activeTabIds.has(tab.id)) {

continue;

}


if (tab.pinned) {

continue;

}


if (!lastActive[tab.id]) {

lastActive[tab.id] = now;

continue;

}


if (isExcluded(tab.url, excludedDomains)) {

continue;

}


const inactiveTime =

now - lastActive[tab.id];

if (inactiveTime >= maxAge) {

try {

await chrome.tabs.remove(tab.id);

delete lastActive[tab.id];

} catch (error) {

delete lastActive[tab.id];

}

}

}

await setLastActive(lastActive);

}


function isExcluded(url, excludedDomains) {

if (!url) {

return false;

}


try {

const hostname =

new URL(url).hostname

.toLowerCase();

return excludedDomains.some(domain => {

domain = domain

.trim()

.toLowerCase()

.replace(/^https?:\/\//, "")

.replace(/^www\./, "")

.replace(/\/.*$/, "");

return (

hostname === domain ||

hostname.endsWith("." + domain)

);

});

} catch {

return false;

}

}


chrome.runtime.onInstalled.addListener(

async () => {

const tabs =

await chrome.tabs.query({});

const lastActive =

await getLastActive();

const now = Date.now();

for (const tab of tabs) {

if (tab.id == null) {

continue;

}

if (!lastActive[tab.id]) {

lastActive[tab.id] = now;

}

}

await setLastActive(lastActive);

}

);

4. Сохраняем файл background.js

5. Внутрь файла popup.html вставляем следующий код:

<!DOCTYPE html>

<html lang="ru">

<head>

<meta charset="UTF-8">

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<meta name="application-name" content="Tab Cleaner">

<meta name="description" content="Настройки автоматической очистки неактивных вкладок">

<meta name="theme-color" content="#ffffff">

<title>Tab Cleaner</title>

<style>

:root {

--popup-width: 280px;

--base-spacing: 16px;

--small-spacing: 5px;

--control-radius: 2px;

--text-size: 14px;

--small-text-size: 12px;

}


* {

box-sizing: border-box;

}


html {

min-width: 280px;

min-height: 100px;

}


body {

width: 280px;

padding: 16px;

margin: 0;

font-family: Arial, sans-serif;

font-size: var(--text-size);

line-height: 1.4;

background: #fff;

}


main {

display: block;

width: 100%;

}


section {

display: block;

width: 100%;

}


label {

display: inline-block;

line-height: 1.4;

}


h3 {

margin-top: 0;

}


.threshold {

display: flex;

gap: 6px;

align-items: center;

}


.threshold input {

width: 70px;

padding: 5px;

box-sizing: border-box;

}


.threshold select {

flex: 1;

padding: 5px;

}


textarea {

width: 100%;

height: 90px;

box-sizing: border-box;

resize: vertical;

font-family: Arial, sans-serif;

font-size: var(--text-size);

line-height: 1.4;

}


input,

select,

textarea,

button {

font: inherit;

}


input:focus,

select:focus,

textarea:focus,

button:focus {

outline-offset: 1px;

}


button {

width: 100%;

margin-top: 12px;

padding: 8px;

cursor: pointer;

min-height: 34px;

}


.hint {

margin-top: 5px;

font-size: 12px;

color: #666;

}


#status {

margin-top: 10px;

font-size: 12px;

min-height: 16px;

line-height: 1.3;

}


.settings {

width: 100%;

display: block;

}


.control-group {

width: 100%;

display: block;

margin-bottom: 8px;

}


.control-group:last-child {

margin-bottom: 0;

}


.spacer {

display: block;

width: 100%;

height: 1px;

}


.visually-contained {

width: 100%;

max-width: 100%;

overflow: hidden;

}

</style>

</head>

<body>

<main class="settings">

<section class="control-group">

<h3>Tab Cleaner</h3>

<label>Закрывать после:</label>

<div class="threshold">

<input id="value" type="number" min="1" value="7">

<select id="unit">

<option value="minutes">минут</option>

<option value="hours">часов</option>

<option value="days" selected>дней</option>

</select>

</div>

<div class="hint">

Отсчитывается с последней активации вкладки.

</div>

</section>


<div class="spacer"></div>


<section class="control-group">

<label>Не закрывать:</label>

<textarea

id="excluded"

placeholder="google.com&#10;youtube.com&#10;mail.google.com"

></textarea>

<div class="hint">

Один домен на строку.

</div>

</section>


<button id="save">Сохранить</button>

<div id="status"></div>

</main>


<script src="popup.js"></script>

</body>

</html>

6. Сохраняем файл popup.html

7. Внутрь файла popup.js вставляем следующий код:

const valueInput = document.getElementById("value");

const unitInput = document.getElementById("unit");

const excludedInput = document.getElementById("excluded");

const saveButton = document.getElementById("save");

const status = document.getElementById("status");


const DEFAULT_THRESHOLD_VALUE = 7;

const DEFAULT_THRESHOLD_UNIT = "days";

const STATUS_CLEAR_DELAY = 1500;

const EMPTY_VALUE = "";

const LINE_SEPARATOR = "\n";

const MIN_THRESHOLD_VALUE = 1;


const UI_ELEMENTS = {

value: valueInput,

unit: unitInput,

excluded: excludedInput,

save: saveButton,

status

};


const STORAGE_FIELDS = {

thresholdValue: "thresholdValue",

thresholdUnit: "thresholdUnit",

excludedDomains: "excludedDomains"

};


const INTERFACE_STATE = {

loaded: false,

saving: false,

ready: false

};


function getDefaultSettings() {

return {

thresholdValue: DEFAULT_THRESHOLD_VALUE,

thresholdUnit: DEFAULT_THRESHOLD_UNIT,

excludedDomains: []

};

}


function normalizeText(value) {

return String(value ?? EMPTY_VALUE);

}


async function loadSettings() {

const data = await chrome.storage.local.get({

thresholdValue: 7,

thresholdUnit: "days",

excludedDomains: []

});


valueInput.value = data.thresholdValue;

unitInput.value = data.thresholdUnit;

excludedInput.value = data.excludedDomains.join("\n");

}


saveButton.addEventListener("click", async () => {

const thresholdValue = Math.max(

1,

Number(valueInput.value) || 1

);


const thresholdUnit = unitInput.value;


const excludedDomains = excludedInput.value

.split("\n")

.map(x => x.trim().toLowerCase())

.filter(Boolean);


await chrome.storage.local.set({

thresholdValue,

thresholdUnit,

excludedDomains

});


status.textContent = "Сохранено ✓";


setTimeout(() => {

status.textContent = "";

}, 1500);

});


loadSettings();

8. Сохраняем файл popup.js

После этого ищем три точки, далее — «Расширения», жмем «Управления расширениями» и далее «Загрузить распакованное расширение».

Ищем папку, которую мы создавали, и, собственно, проверяем, чтобы все работало как задумано.

Саммари

Как видите, параноидально закрывать вкладки сразу же после использования, равно как и зарываться в десятки вкладок, вовсе не обязательно — достаточно создать расширение, которое закрывало бы вкладки за вас. И теперь вы знаете, как это сделать.

Здравствуйте! У вас включен блокировщик рекламы, часть сайта не будет работать!