Как создать расширение, удаляющее мусор с сайтов
Traffic Cardinal Traffic Cardinal  написал 13.08.2026

Как создать расширение, удаляющее мусор с сайтов

Traffic Cardinal Traffic Cardinal  написал 13.08.2026
11 мин
0
34
Содержание

Продолжаем упрощать жизнь интернет-пользователей с помощью браузерных расширений. Напомним, что мы уже рассказывали, как создать:

Сегодня же мы создадим расширение, убирающее мусор.

Что есть мусор? Здесь каждый решает сам. Формально речь о любом элементе сайта. Однако, по задумке, это разного рода реклама/попапы и прочий «мусор», который перекрывает обзор и мешает пользоваться сайтом. Хотя ничто не мешает удалять любые другие элементы. И даже «родительские элементы» этих элементов (что полезно, если на попапе много мелких деталей).

Немного важной инфы

Заранее информируем, что в статье опущены базовые принципы работы расширений. Если вы еще не занимались подобным — вам сюда. Там в деталях разобрано, что, как и почему. Если же вы уже имеете опыт создания расширений — то это вам ни к чему. Само расширение будет состоять из:

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

  • content.js — файл с основной логикой работы.

  • background.js — файл с логикой работы контекстного меню. Увы, специфика MV3 не позволяет взаимодействовать с контекстным меню из content.js, а «переделывать» его — это автоматически получить проблемы совместимости с другими расширениями, изменяющими его. Поэтому — background.js.

  • popup.html — менюшка управления включением/выключением расширения на сайтах.

  • popup.js — логика описанной выше менюшки.

Создаем расширение, удаляющее мусор с сайтов

Итак, начнем. Для начала создаем папку с любым названием (лучше без кириллицы), а в ней создаем 5 ранее упомянутых файлов — manifest.json, content.js, background.js, popup.html, popup.js. Затем делаем следующее:

1. Открываем manifest.json и копируем в него:

{

"manifest_version":3,

"name":"Element Cleaner Per Site",

"version":"2.0.0",

"description":"Удаление элементов страниц через контекстное меню с включением по отдельным сайтам.",

"permissions":[

"contextMenus",

"tabs",

"storage",

"activeTab"

],

"host_permissions":[

"<all_urls>"

],

"background":{

"service_worker":"background.js"

},

"action":{

"default_title":"Element Cleaner",

"default_popup":"popup.html"

},

"content_scripts":[

{

"matches":[

"<all_urls>"

],

"js":[

"content.js"

],

"run_at":"document_start"

}

]

}

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

3. Открываем content.js и копируем в него:

let selectedElement=null;

let hoverElement=null;

let extensionEnabled=false;

let lastOutline="";

function checkState(){

chrome.storage.sync.get(

["enabledSites"],

result=>{

const sites=

Array.isArray(result.enabledSites)

? result.enabledSites

: [];

extensionEnabled=

sites.includes(location.hostname);

if(!extensionEnabled){

removeHighlight();

}

}

);

}

function applyHighlight(element){

if(!element) return;

if(hoverElement===element){

return;

}

removeHighlight();

hoverElement=element;

lastOutline=

element.style.outline;

element.style.outline=

"3px solid red";

element.style.outlineOffset=

"2px";

}

function removeHighlight(){

if(hoverElement){

hoverElement.style.outline=

lastOutline || "";

hoverElement.style.outlineOffset="";

}

hoverElement=null;

lastOutline="";

}

document.addEventListener(

"mousemove",

event=>{

if(!extensionEnabled){

return;

}

const element=

event.target;

if(

element &&

element!==document.body &&

element!==document.documentElement

){

applyHighlight(element);

}

},

true

);

document.addEventListener(

"mouseleave",

()=>{

removeHighlight();

},

true

);

document.addEventListener(

"contextmenu",

event=>{

if(!extensionEnabled){

return;

}

selectedElement=

event.target;

applyHighlight(selectedElement);

},

true

);

chrome.storage.onChanged.addListener(()=>{

checkState();

});

chrome.runtime.onMessage.addListener(

message=>{

if(message.action==="remove_element"){

if(

extensionEnabled &&

selectedElement

){

selectedElement.remove();

selectedElement=null;

}

}

if(message.action==="remove_parent_element"){

if(

extensionEnabled &&

selectedElement

){

const parent=

selectedElement.parentElement;

if(parent){

parent.remove();

}

selectedElement=null;

}

}

}

);

checkState();

4. Сохраняем content.js

5. Открываем background.js и копируем в него:

const REMOVE_MENU_ID="remove_element";

const REMOVE_PARENT_MENU_ID="remove_parent_element";

function getHostname(url){

try{

return new URL(url).hostname;

}catch(e){

return "";

}

}

function getEnabledSites(){

return new Promise(resolve=>{

chrome.storage.sync.get(

["enabledSites"],

result=>{

resolve(

Array.isArray(result.enabledSites)

? result.enabledSites

: []

);

}

);

});

}

async function isEnabledForTab(tab){

if(!tab || !tab.url){

return false;

}

const hostname=getHostname(tab.url);

const sites=await getEnabledSites();

return sites.includes(hostname);

}

function createContextMenu(){

chrome.contextMenus.removeAll(()=>{

chrome.contextMenus.create({

id:REMOVE_MENU_ID,

title:"Удалить элемент",

contexts:["all"]

});

chrome.contextMenus.create({

id:REMOVE_PARENT_MENU_ID,

title:"Удалить родительский элемент",

contexts:["all"]

});

});

}

async function refreshMenu(tabId,url){

const enabled=

(await getEnabledSites())

.includes(getHostname(url));

if(enabled){

createContextMenu();

}else{

chrome.contextMenus.removeAll();

}

}

chrome.runtime.onInstalled.addListener(()=>{

chrome.contextMenus.removeAll();

});

chrome.storage.onChanged.addListener(()=>{

chrome.tabs.query(

{},

tabs=>{

tabs.forEach(tab=>{

if(tab.url){

refreshMenu(

tab.id,

tab.url

);

}

});

}

);

});

chrome.tabs.onActivated.addListener(info=>{

chrome.tabs.get(

info.tabId,

tab=>{

if(tab && tab.url){

refreshMenu(

tab.id,

tab.url

);

}

}

);

});

chrome.tabs.onUpdated.addListener(

(tabId,changeInfo,tab)=>{

if(changeInfo.url){

refreshMenu(

tabId,

changeInfo.url

);

}

}

);

chrome.contextMenus.onClicked.addListener(

async(info,tab)=>{

if(!await isEnabledForTab(tab)){

return;

}

chrome.tabs.sendMessage(

tab.id,

{

action:info.menuItemId

}

);

}

);

6. Сохраняем background.js

7. Открываем popup.html и копируем в него:

<!DOCTYPE html>

<html lang="ru">

<head>

<meta charset="UTF-8">

<title>Element Cleaner</title>

<style>

body{

width:350px;

padding:14px;

margin:0;

font-family:Arial,sans-serif;

background:#f7f7f7;

color:#222;

}

h2{

margin:0 0 12px 0;

font-size:20px;

}

.description{

font-size:12px;

opacity:.75;

line-height:1.4;

margin-bottom:12px;

}

.panel{

background:white;

border:1px solid #d5d5d5;

padding:10px;

margin-bottom:12px;

}

.caption{

font-size:12px;

opacity:.7;

}

.current-site{

margin-top:6px;

font-weight:bold;

word-break:break-all;

}

.status{

margin-top:8px;

padding:6px;

text-align:center;

border:1px solid #ddd;

font-size:12px;

}

button{

width:100%;

padding:8px;

margin-top:8px;

cursor:pointer;

}

.sites-title{

margin-bottom:8px;

}

.sites-list{

max-height:170px;

overflow-y:auto;

border:1px solid #ddd;

background:#fff;

}

.site-row{

display:flex;

justify-content:space-between;

align-items:center;

padding:6px;

border-bottom:1px solid #eee;

font-size:12px;

}

.site-name{

overflow:hidden;

text-overflow:ellipsis;

}

.delete-button{

width:auto;

margin:0;

padding:3px 8px;

}

.empty{

padding:8px;

opacity:.6;

font-size:12px;

}

.footer{

margin-top:10px;

text-align:center;

font-size:11px;

opacity:.5;

}

</style>

</head>

<body>

<h2>

Element Cleaner

</h2>

<div class="description">

Расширение полностью включается только на выбранных сайтах.

На остальных страницах оно не добавляет пункты в контекстное меню.

</div>

<div class="panel">

<div class="caption">

Текущий сайт:

</div>

<div

id="currentSite"

class="current-site"

>

Определение...

</div>

<div

id="status"

class="status"

>

Проверка состояния...

</div>

<button

id="toggleButton"

>

Загрузка...

</button>

</div>

<div class="panel">

<div class="sites-title">

Активные сайты:

</div>

<div

id="sitesList"

class="sites-list"

>

</div>

<button

id="addButton"

>

Добавить текущий сайт

</button>

</div>

<div class="footer">

Element Cleaner • Site control

</div>

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

</body>

</html>

8. Сохраняем popup.html

Примерно так это будет выглядеть
Примерно так это будет выглядеть

9. Открываем popup.js и копируем в него:

const currentSiteElement =

document.getElementById("currentSite");

const statusElement =

document.getElementById("status");

const toggleButton =

document.getElementById("toggleButton");

const addButton =

document.getElementById("addButton");

const sitesListElement =

document.getElementById("sitesList");

let currentHostname="";

let enabledSites=[];

function getCurrentTab(){

return new Promise(resolve=>{

chrome.tabs.query(

{

active:true,

currentWindow:true

},

tabs=>{


if(tabs && tabs[0]){


resolve(tabs[0]);


}else{


resolve(null);

}

}

);

});

}

function extractHostname(url){

try{

return new URL(url).hostname;

}catch(error){

return "";

}

}

function loadEnabledSites(){

return new Promise(resolve=>{

chrome.storage.sync.get(

["enabledSites"],

result=>{

if(

result &&

Array.isArray(result.enabledSites)

){

resolve(

result.enabledSites

);

}else{

resolve([]);

}

}

);

});

}

function saveEnabledSites(list){

return new Promise(resolve=>{

chrome.storage.sync.set(

{

enabledSites:list

},

()=>{

resolve();

}

);

});

}

function isEnabled(site){

return enabledSites.includes(site);

}

function updateStatus(){

if(isEnabled(currentHostname)){

statusElement.textContent=

"Расширение активно на этом сайте";

toggleButton.textContent=

"Выключить расширение на сайте";

}else{

statusElement.textContent=

"Расширение выключено на этом сайте";

toggleButton.textContent=

"Включить расширение на сайте";

}

}

function renderCurrentSite(){

currentSiteElement.textContent=

currentHostname || "Не найден";

updateStatus();

}

function renderSites(){

sitesListElement.innerHTML="";

if(enabledSites.length===0){

const empty=

document.createElement("div");

empty.className="empty";

empty.textContent=

"Нет активных сайтов";

sitesListElement.appendChild(empty);

return;

}

enabledSites.forEach(site=>{

const row=

document.createElement("div");

row.className=

"site-row";

const name=

document.createElement("div");

name.className=

"site-name";

name.textContent=

site;

const button=

document.createElement("button");

button.className=

"delete-button";

button.textContent=

"Удалить";

button.onclick=

async()=>{

enabledSites=

enabledSites.filter(

item=>

item!==site

);

await saveEnabledSites(

enabledSites

);

renderSites();

renderCurrentSite();

};

row.appendChild(name);

row.appendChild(button);

sitesListElement.appendChild(row);

});

}

async function toggleSite(){

if(!currentHostname){

return;

}

if(isEnabled(currentHostname)){

enabledSites=

enabledSites.filter(

item=>

item!==currentHostname

);

}else{

enabledSites.push(

currentHostname

);

}

await saveEnabledSites(

enabledSites

);

renderSites();

renderCurrentSite();

}

async function addCurrentSite(){

if(!currentHostname){

return;

}

if(!isEnabled(currentHostname)){

enabledSites.push(

currentHostname

);

await saveEnabledSites(

enabledSites

);

}

renderSites();

renderCurrentSite();

}

function bindEvents(){

toggleButton.onclick=

toggleSite;

addButton.onclick=

addCurrentSite;

}

async function init(){

const tab=

await getCurrentTab();

if(!tab){

return;

}

currentHostname=

extractHostname(tab.url);

enabledSites=

await loadEnabledSites();

renderCurrentSite();

renderSites();

bindEvents();

}

document.addEventListener(

"DOMContentLoaded",

init

);

10. Сохраняем popup.js

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

Находим нашу папку и выбираем ее. Тестируем, все ли работает, не забыв предварительно включить расширение для конкретного сайта.

Саммари

Как видите, удалять с сайта элементы, которые вам мешают, не так уж и сложно — достаточно уделить пару минут и сделать для этого специальное разрешение. И если в случае с безобидным попапом слева снизу это не столь актуально, то в случае с, скажем, огромным окном на весь экран, мешающим пользоваться сайтом, начинаешь понимать, насколько это удобно. Тем более что собственное приложение не имеет сигнатур AdBlock и не палится, что, в свою очередь, позволяет заходить на сайты, которые блокируют подключение при обнаружении AdBlock.

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