Каждый день мы делимся ссылками в мессенджерах, копируем их из браузера или кликаем по ним. И почти всегда к реальному адресу прилипает хвост из UTM-меток, fbclid, gclid и прочего мусора, которым маркетологи и платформы помечают наш трафик.
Создать расширение, которое отрезает этот хвост с помощью регулярного выражения — задача на 10 минут. Но сделать так, чтобы это расширение не сломало работу сайтов, не убило поисковики и не вызвало бесконечные циклы переадресации в современных реалиях Manifest V3 — уже интереснее.
Недавно я завершил работу над расширением ZeroTail и хочу поделиться несколькими неочевидными подводными камнями, с которыми столкнулся при реализации авто-очистки URL.
Проблема №1: Слепая чистка ломает поиск
Первое желание — просто вырезать все известные параметры из URL.searchParams у любой ссылки. Но если мы применим это правило ко всем сайтам без разбора, мы сломаем поисковики.
Например, Google использует параметры source и medium для сортировки результатов поиска (кнопки "Картинки", "Видео" и т.д.). Если мы их вырежем, поиск превратится в тыкву.
Решение: Я ввел жесткий whitelist (белый список) для доменов, где очистка отключена на корневом уровне.
const WHITELIST = [ 'google.com', 'bing.com', 'yandex.ru', 'duckduckgo.com' ]; async function cleanUrl(url) { const urlObj = new URL(url); if (WHITELIST.some(site => urlObj.hostname.includes(site))) { return url; // Не трогаем поисковики } // ... логика очистки }
Проблема №2: Бесконечные редиректы и сломанные XHR
В эпоху Manifest V2 фоновой скрипт мог перехватить любой webRequest и изменить redirectUrl. В Manifest V3 мы ограничены declarativeNetRequest (которая не умеет динамически парсить параметры строк) или событием webNavigation.onBeforeNavigate.
Если просто слушать onBeforeNavigate и делать chrome.tabs.update({ url: cleanedUrl }), мы наступим на грабли:
Сайт сделает POST-запрос (например, отправка формы) — мы его сломаем.
Сайт сделает XHR/Fetch запрос за JSON — мы обрушим JS.
Мы сделаем редирект на очищенный URL, что снова вызовет
onBeforeNavigate, и мы улетим в бесконечный цикл.
Решение: Проверять transitionType и типы схем. Нам нужно реагировать только на классические клики по ссылкам.
chrome.webNavigation.onBeforeNavigate.addListener(async (details) => { // Получаем настройки (функция авто-очистки может быть выключена) const result = await chrome.storage.sync.get(['autoClean']); if (!result.autoClean) return; // ВАЖНО: реагируем только на переходы по ссылкам (link), // игнорируем typing, auto_subframe, form_submit и т.д. if (details.transitionType !== 'link') return; const cleanedUrl = await cleanUrl(details.url); // Если URL изменился — перенаправляем if (details.url !== cleanedUrl) { chrome.tabs.update(details.tabId, { url: cleanedUrl }); } }, { url: [ {hostContains: '.'}, {schemes: ['http', 'https']} ] });
Примечание: фильтр hostContains: '.' нужен, чтобы не пытаться чистить внутренние страницы расширения (например, chrome-extension://...), так как new URL() с такими схемами в контексте веб-страницы может вести себя непредсказуемо.
Проблема №3: Как красиво скопировать ссылку без лишних API
Иногда авто-очистка не нужна (например, вы хотите отправить грязную ссылку коллеге, чтобы он увидел, откуда вы пришли, но сами хотите скопировать её чистую версию). Для этого я добавил пункт в контекстное меню браузера.
В Manifest V3 нельзя напрямую писать в буфер обмена из фонового Service Worker'а. Нужно делегировать это Content Script'у на странице.
В background.js вызываем контекстное меню:
chrome.contextMenus.onClicked.addListener((info, tab) => { if (info.menuItemId === "cleanCopy" && info.linkUrl) { chrome.tabs.sendMessage(tab.id, { action: "cleanAndCopy", url: info.linkUrl }); } });
А в content.js используем современный Async Clipboard API с минимальным и ненавязчивым UI (вместо тяжелых алертов):
function cleanAndCopyLink(url) { chrome.runtime.sendMessage({ action: "cleanLink", url: url }, (response) => { if (response && response.cleanedUrl) { navigator.clipboard.writeText(response.cleanedUrl).then(() => { showNotification(`Ссылка очищена и скопирована!\nУдалено трекеров: ${response.trackerCount}`); }); } }); } function showNotification(message) { let toast = document.getElementById('zerotail-toast'); if (!toast) { toast = document.createElement('div'); toast.id = 'zerotail-toast'; // Стилизация под Material Design / современные тосты toast.style.cssText = ` position: fixed; bottom: 20px; right: 20px; background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); color: white; padding: 12px 20px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); z-index: 999999; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 320px; transition: opacity 0.3s ease; `; document.body.appendChild(toast); } toast.textContent = message; toast.style.opacity = '1'; setTimeout(() => { toast.style.opacity = '0'; }, 3000); }
Итоги
Написание "простого" парсера ссылок в 2026 году требует учета специфики Service Worker'ов в Manifest V3 и бережного отношения к навигационным событиям браузера. Ошибки в transitionType или отсутствие whitelist'ов мгновенно превращают полезный инструмент в источник головной боли для пользователя.
P.S. Описанная выше архитектура реализована в виде готового расширения ZeroTail для Chrome. Код на данный момент закрыт, но расширение абсолютно бесплатное, не требует регистрации и собирает ноль данных о пользователях. В нем есть как авто-очистка при переходе, так и ручное управление белыми/черными списками параметров.



