Cleaning Tracker Parameters from URLs in Manifest V3: Avoiding Common Pitfalls
Browser extension developers often face the task of removing UTM tags, fbclid, gclid, and other tracking parameters from URLs. A simple implementation using regular expressions works for basic cases, but in Manifest V3, it leads to failures: breaking search engines, causing infinite redirects, and disrupting XHR requests. Let's explore key issues and their solutions with a real-world architecture example.
A whitelist for search engines prevents the removal of necessary parameters. Google, Yandex, and others use source and medium for filters (e.g., images, videos). Without exceptions, cleaning renders search non-functional.
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; // Leave search engines untouched
}
// ... cleaning logic
}
Navigation in Manifest V3: Filtering Transitions
Manifest V3 restricts request interception. declarativeNetRequest doesn't parse parameters dynamically, so webNavigation.onBeforeNavigate is used. A simple redirect via chrome.tabs.update causes problems:
- POST requests from forms break.
- XHR/Fetch requests for JSON fail.
- The redirect triggers the event repeatedly, creating a loop.
The solution is strict transitionType checking. Only handle 'link' (link clicks), ignoring typing, auto_subframe, and form_submit.
chrome.webNavigation.onBeforeNavigate.addListener(async (details) => {
const result = await chrome.storage.sync.get(['autoClean']);
if (!result.autoClean) return;
if (details.transitionType !== 'link') return;
const cleanedUrl = await cleanUrl(details.url);
if (details.url !== cleanedUrl) {
chrome.tabs.update(details.tabId, { url: cleanedUrl });
}
}, {
url: [
{hostContains: '.'},
{schemes: ['http', 'https']}
]
});
The hostContains: '.' filter excludes chrome-extension:// schemes, where new URL() behaves unpredictably.
Context Menu and Clipboard API
For manual link cleaning, add a context menu. In Manifest V3 Service Workers, clipboard access is blocked—delegate to a content script.
In 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 script uses the Async Clipboard API with a toast notification:
function cleanAndCopyLink(url) {
chrome.runtime.sendMessage({ action: "cleanLink", url: url }, (response) => {
if (response && response.cleanedUrl) {
navigator.clipboard.writeText(response.cleanedUrl).then(() => {
showNotification(`Link cleaned and copied!\nTrackers removed: ${response.trackerCount}`);
});
}
});
}
function showNotification(message) {
let toast = document.getElementById('zerotail-toast');
if (!toast) {
toast = document.createElement('div');
toast.id = 'zerotail-toast';
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);
}
Styling mimics Material Design: gradient background, shadow, smooth fade.
Key Takeaways
- Domain whitelist: Excludes search engines (google.com, yandex.ru) where parameters are critical for functionality.
- transitionType === 'link': Prevents interference with forms, XHR, and URL input.
- Scheme filter: hostContains: '.' + http/https avoids errors with extension://.
- Clipboard delegation: Service Worker → Content Script for Manifest V3.
- Toast UI: Minimalist notifications without alerts, with a count of removed trackers.
— Editorial Team
No comments yet.