Skip to main content
Ad Bear icon

Ad Bear

Jo

ADBear is a comprehensive Network Monitoring and DNS filtering extension for firefox

Bilder in Mozilla Add-ons

AI code review · Qwen3-235B

What changed between versions

This section shows what the Qwen3 model detected in the source code of the last iteration of this extension — new features, permission changes, data flow, and end-user impact.

Overall risk

3/100

Low

Iteration 1 · feature_addition

v0.0.1 → v0.1.0

Low risk
Risk score3/100

What this update means for you

Layperson summary from Qwen3

Das Update verbessert die Blockierung von Werbung und Tracker mit besserer Erkennung von problematischen Seiten. Es fügt neue Filterquellen hinzu und macht die Erweiterung stabiler auf Mobilgeräten. Das Update ist sicher und sinnvoll.

Ad Bear überwacht Netzwerkverbindungen, nutzt GeoIP, Bedrohungsdaten und Blocklisten, um Domains und IPs zu blockieren.

Privacy impact

Gering: Keine neuen sensiblen Berechtigungen oder Datenabflüsse, aber Kontakt zu externen Blocklisten.

What users will notice

Behaviour or UI changes the end user sees

  • Neue Rechtsklick-Optionen durch Kontextmenüs
  • Bessere Blockierung von Subdomains basierend auf Eltern-Domains
  • Hinweis im Log bei fehlendem nativeMessaging (z. B. auf Mobilgeräten)

Feature changes

Added

  • Unterstützung für Kontextmenüs via 'contextMenus'-Berechtigung
  • Verbesserte Domain-Blockierung mit Whitelist-Überprüfung entlang der Eltern-Domain-Kette
  • Erweiterte IP-Blockierung nur bei literalen IP-Adressen
  • Erkennung fehlender nativeMessaging-Unterstützung (z. B. auf Mobilgeräten)

Permission & access changes

What the extension can now touch that it couldn't before

New permissions requested

  • contextMenus

Code changes

6 modified code files · raw diff available · click to unfold

Technical changes (Qwen summary)

  • Robustere Domain- und IP-Block-Logik mit vollständiger Parent-Chain-Prüfung
  • Sichere nativeMessaging-Verbindung nur, wenn verfügbar
  • Explizite Versionsanforderung ab Firefox 115.0

Data flow

  • https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
  • https://big.oisd.nl/
  • https://api.ipify.org/

Raw line-level diff

+310 added23 removed6 files with line-level diff
background.js+445
@@ -191,17 +191,31 @@
// ==================== WEBREQUEST LISTENERS ====================
function isDomainBlocked(domain) {
if (!filteringEnabled || !domain) return false;
const d = domain.toLowerCase().replace(/\.$/, "");
// Whitelist check (walk parent chain)
let current = d;
while (current) {
if (whitelistedDomains.has(current)) return false;
const firstDot = current.indexOf(".");
if (firstDot === -1) break;
current = current.substring(firstDot + 1);
if (!current.includes(".")) break;
}
if (whitelistedDomains.has(domain)) return false;
if (blockedIps.has(domain)) return true;
// Domain block check (walk parent chain)
current = d;
let current = domain;
while (current) {
if (blockedDomains.has(current)) return true;
const firstDot = current.indexOf(".");
if (firstDot === -1) break;
current = current.substring(firstDot + 1);
if (!current.includes(".")) break;
if (!current.includes(".")) break; // Don't block TLDs
}
// IP-based block: only if domain is literally an IP
if (/^(\d{1,3}\.){3}\d{1,3}$/.test(d) && blockedIps.has(d)) return true;
return false;
}
@@ -359,6 +373,7 @@
let nativeRetries = 0;
function connectNative() {
if (typeof browser.runtime.connectNative === "undefined") return; // Android
if (connecting || nativePort) return;
connecting = true;
if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null; }
@@ -440,7 +455,11 @@
if (data.filteringEnabled) filteringEnabled = data.filteringEnabled;
}).catch(() => {});
if (typeof browser.runtime.connectNative !== "undefined") {
connectNative();
} else {
console.info("Ad Bear: native messaging unavailable (mobile) — GeoIP/bandwidth disabled");
}
connectNative();
// ==================== MESSAGE HANDLER ====================
browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
@@ -540,7 +559,27 @@
resolveDomain(message.domain).then((resolved) => {
sendResponse({ success: !!resolved, addresses: resolved ? resolved.result.addresses : [] });
}).catch(() => sendResponse({ success: false, addresses: [] }));
return true;
case "get_platform":
(async () => {
try {
const info = await browser.runtime.getPlatformInfo();
sendResponse({
os: info.os, arch: info.arch,
isAndroid: info.os === "android",
hasSidebar: typeof browser.sidebarAction !== "undefined",
hasContextMenus: typeof browser.contextMenus !== "undefined",
hasNativeMessaging: typeof browser.runtime.connectNative !== "undefined",
});
} catch { sendResponse({ os: "unknown", isAndroid: false }); }
})();
return true;
default:
console.warn("Ad Bear: unknown action:", message.action);
sendResponse({ error: "unknown_action", action: message.action });
return false;
}
});
manifest.json+42
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "Ad Bear",
"version": "0.1.0",
"version": "0.0.1",
"description": "Monitor network connections with GeoIP, threat intel, blocklists, and firewall control",
"browser_specific_settings": {
"gecko": {
@@ -10,7 +10,8 @@
"required": [
"websiteActivity"
]
},
"strict_min_version": "115.0"
}
}
},
"permissions": [
@@ -20,6 +21,7 @@
"webNavigation",
"dns",
"tabs",
"contextMenus",
"nativeMessaging"
],
"host_permissions": [
popup/popup.js+503
@@ -58,6 +58,22 @@
await browser.runtime.sendMessage({ action: "set_filtering", enabled });
dnsFilterStatus.textContent = enabled ? "On" : "Off";
dnsFilterStatus.className = enabled ? "toggle-status active" : "toggle-status";
// If enabling filtering, check if blocklists are loaded.
// If not, open the sidebar/dashboard so the user can download them.
if (enabled) {
const stats = await browser.runtime.sendMessage({ action: "get_stats" });
if (stats && stats.domainCount === 0) {
dnsFilterStatus.textContent = "On — download lists!";
dnsFilterStatus.className = "toggle-status warning";
// Open dashboard so user can download blocklists
if (!isAndroid && typeof browser.sidebarAction !== "undefined") {
try { await browser.sidebarAction.open(); } catch {}
} else {
try { await browser.tabs.create({ url: browser.runtime.getURL("sidebar/sidebar.html") }); } catch {}
}
}
}
} catch (e) {
dnsFilterToggle.checked = !enabled;
}
@@ -69,11 +85,42 @@
ping();
fetchDNSStats();
});
let isAndroid = false;
async function detectPlatform() {
try {
const info = await browser.runtime.getPlatformInfo();
isAndroid = info.os === "android";
} catch { isAndroid = false; }
if (isAndroid) {
if (openSidebar) openSidebar.textContent = "Open Dashboard";
document.body.classList.add("mobile");
} else if (typeof browser.sidebarAction === "undefined") {
if (openSidebar) openSidebar.textContent = "Open Dashboard";
}
}
openSidebar.addEventListener("click", async () => {
// Desktop: open sidebar. Android/fallback: open in new tab.
if (!isAndroid && typeof browser.sidebarAction !== "undefined") {
try {
await browser.sidebarAction.open();
window.close();
return;
} catch (e) {
console.warn("sidebarAction.open failed, falling back to tab:", e);
}
}
try {
await browser.tabs.create({ url: browser.runtime.getURL("sidebar/sidebar.html") });
window.close();
} catch (e) {
console.error("Could not open dashboard:", e);
}
openSidebar.addEventListener("click", () => {
browser.sidebarAction.open();
window.close();
});
detectPlatform();
ping();
fetchDNSStats();
popup/popup.css+110
@@ -371,4 +371,15 @@
::-webkit-scrollbar-thumb:hover {
background: var(--border-light);
}
/* ===== Mobile (Android) ===== */
body.mobile #app { min-width: 280px; max-width: 100vw; }
body.mobile .header-right { display: none; }
body.mobile .stats-grid { grid-template-columns: 1fr; }
body.mobile .stat-card { padding: 8px 12px; }
body.mobile .dns-toggle-bar { padding: 12px; }
body.mobile .section-label { padding: 8px 12px 4px; }
body.mobile button, body.mobile .toggle-switch { min-height: 44px; }
body.mobile .header { padding-top: max(12px, env(safe-area-inset-top)); }
sidebar/sidebar.js+9313
@@ -1,3 +1,35 @@
// ===== Mobile detection (Android compatibility) =====
(async function detectMobile() {
try {
const info = await browser.runtime.getPlatformInfo();
if (info.os === "android") {
document.body.classList.add("mobile");
console.debug("Ad Bear: mobile mode (Android)");
}
} catch {}
if (window.innerWidth < 600) document.body.classList.add("mobile");
window.addEventListener("resize", () => {
if (window.innerWidth < 600) document.body.classList.add("mobile");
else document.body.classList.remove("mobile");
});
})();
async function checkNativeAvailability() {
try {
const platform = await browser.runtime.sendMessage({ action: "get_platform" });
if (platform && !platform.hasNativeMessaging) {
const banner = document.createElement("div");
banner.style.cssText = "background:#fef3c7;color:#92400e;padding:8px 12px;font-size:12px;border-bottom:1px solid #fcd34d;";
banner.textContent = "GeoIP/bandwidth unavailable on mobile. Connection tracking still works.";
document.body.insertBefore(banner, document.body.firstChild);
document.querySelectorAll(".requires-native").forEach(el => el.style.display = "none");
}
} catch {}
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", checkNativeAvailability);
} else { checkNativeAvailability(); }
let connections = [];
let filteredConnections = [];
let previousConnections = new Set();
@@ -1460,7 +1492,7 @@
browser.runtime.sendMessage({
action: "update_blocklist",
domains: [...dnsFilteringDomains].slice(0, 100000),
ips: [], // IPs no longer used (parser now extracts domains)
ips: [...autoBlocklistIps],
whitelist: [...whitelistedDomains],
enabled: dnsFilteringEnabled
}).catch(() => {});
@@ -1600,31 +1632,79 @@
async function fetchBlocklistFeed(url) {
try {
const resp = await fetch(url);
if (!resp.ok) {
console.warn(`Ad Bear: blocklist ${url} returned HTTP ${resp.status}`);
return new Set();
}
if (!resp.ok) return new Set();
const text = await resp.text();
const domains = new Set();
// Regex: valid domain (RFC 1035 simplified) — excludes pure IPs
const domainRegex = /^(?!^\d+\.\d+\.\d+\.\d+$)[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+$/i;
const ips = new Set();
const ipRegex = /^(\d{1,3}\.){3}\d{1,3}$/;
for (const line of text.split("\n")) {
const t = line.trim();
// Skip comments and empty lines
if (!t || t.startsWith("#") || t.startsWith("!")) continue;
// AdBlock format: ||example.com^ or ||example.com^$options
if (t.startsWith("||")) {
const match = t.match(/^\|\|([a-z0-9.-]+)\^?/i);
if (match) {
const d = match[1].toLowerCase();
// Filter out IPs and invalid domains
if (!ipRegex.test(d) && domainRegex.test(d)) {
domains.add(d);
}
}
continue;
}
// Hosts format: IP domain [aliases...]
// e.g. "0.0.0.0 example.com" or "127.0.0.1 example.com www.example.com"
if (!t || t.startsWith("#")) continue;
const parts = t.split(/\s+/);
if (parts.length >= 2 && ipRegex.test(parts[0])) {
// Take all parts after the IP — they're domain aliases
for (let i = 1; i < parts.length; i++) {
const d = parts[i].toLowerCase();
if (domainRegex.test(d) && d !== "localhost" && d !== "ip6-localhost" && d !== "broadcasthost") {
domains.add(d);
}
}
continue;
}
// Domain-only line (some lists are just "example.com" one per line)
if (parts.length === 1) {
const d = parts[0].toLowerCase();
if (domainRegex.test(d) && !ipRegex.test(d)) {
domains.add(d);
}
continue;
}
if (ipRegex.test(parts[0]) && parts[0] !== "0.0.0.0" && parts[0] !== "127.0.0.1") ips.add(parts[0]);
else if (parts.length >= 2 && ipRegex.test(parts[1]) && parts[1] !== "0.0.0.0" && parts[1] !== "127.0.0.1") ips.add(parts[1]);
}
console.debug(`Ad Bear: parsed ${domains.size} domains from ${url.split("/").pop()}`);
return domains;
} catch (e) {
console.warn(`Ad Bear: failed to fetch blocklist ${url}:`, e.message);
return new Set();
}
return ips;
} catch (e) { return new Set(); }
}
async function updateAutoBlocklist() {
if (!blocklistAutoToggle.checked) { blocklistStatus.textContent = "Auto-download disabled"; return; }
blocklistStatus.textContent = "Downloading...";
for (const feed of BLOCKLIST_FEEDS) {
blocklistStatus.textContent = `Downloading ${feed.name}...`;
const domains = await fetchBlocklistFeed(feed.url);
for (const d of domains) dnsFilteringDomains.add(d);
blocklistStatus.textContent = `${feed.name}: ${dnsFilteringDomains.size} domains total`;
const ips = await fetchBlocklistFeed(feed.url);
for (const ip of ips) autoBlocklistIps.add(ip);
}
try { localStorage.setItem(AUTO_BLOCKLIST_KEY, JSON.stringify([...autoBlocklistIps])); localStorage.setItem(AUTO_BLOCKLIST_KEY + "_updated", new Date().toLocaleString()); } catch (e) {}
try { await browser.runtime.sendMessage({ action: "update_blocklist", domains: [...dnsFilteringDomains].slice(0, 100000), ips: [], whitelist: [...whitelistedDomains], enabled: dnsFilteringEnabled }); } catch (e) {}
try { await browser.runtime.sendMessage({ action: "update_blocklist", domains: [...dnsFilteringDomains].slice(0, 100000), ips: [...autoBlocklistIps], whitelist: [...whitelistedDomains], enabled: dnsFilteringEnabled }); } catch (e) {}
updateBlocklistStatus();
render();
}
@@ -1686,7 +1766,7 @@
for (const d of manualBlockedDomains) all.add(d);
const domains = [...all];
try { localStorage.setItem("ipwatch_dns_filtering_domains", JSON.stringify(domains)); localStorage.setItem("ipwatch_dns_filtering_updated", new Date().toLocaleString()); } catch (e) {}
try { await browser.runtime.sendMessage({ action: "update_blocklist", domains: domains.slice(0, 100000), ips: [], whitelist: [...whitelistedDomains], enabled: true }); } catch (e) {}
try { await browser.runtime.sendMessage({ action: "update_blocklist", domains: domains.slice(0, 100000), ips: [...autoBlocklistIps], whitelist: [...whitelistedDomains], enabled: true }); } catch (e) {}
dnsFilteringUpdateBtn.disabled = false;
updateDnsFilteringStatus();
}
@@ -2605,7 +2685,7 @@
await browser.runtime.sendMessage({
action: "update_blocklist",
domains: dnsFilteringDomains.slice(0, 100000),
ips: [], // IPs no longer used (parser now extracts domains)
ips: [...autoBlocklistIps],
whitelist: [...whitelistedDomains],
enabled: dnsFilteringEnabled
});
sidebar/sidebar.css+1080
@@ -3331,5 +3331,113 @@
input[type="text"]:focus {
border-color: var(--accent);
}
/* ==================== MOBILE RESPONSIVE SIDEBAR ====================
Add this to the end of sidebar/sidebar.css.
On Android, the sidebar opens as a full tab (since sidebarAction
isn't supported). The styles need to:
1. Fill the viewport (no fixed sidebar width)
2. Use larger touch targets (min 44px)
3. Stack panels vertically instead of multi-column
4. Increase font sizes for readability
*/
/* Detect mobile via body.mobile class (set by sidebar.js on load) */
body.mobile {
width: 100vw;
height: 100vh;
max-width: 100vw;
overflow-x: hidden;
}
body.mobile .sidebar-container {
width: 100%;
height: 100vh;
flex-direction: column;
}
body.mobile .sidebar-panels {
grid-template-columns: 1fr;
gap: 8px;
padding: 8px;
}
body.mobile .stat-card,
body.mobile .panel,
body.mobile .panel-section {
min-height: 44px; /* iOS/Android touch target */
padding: 12px 16px;
font-size: 14px;
}
body.mobile button,
body.mobile .btn,
body.mobile .toggle-switch {
min-height: 44px;
min-width: 44px;
}
body.mobile .toggle-slider {
width: 48px;
height: 28px;
}
body.mobile .header {
padding: 12px 16px;
font-size: 16px;
}
body.mobile .section-label {
font-size: 13px;
padding: 12px 16px 6px;
}
body.mobile .stats-grid {
grid-template-columns: repeat(2, 1fr);
gap: 8px;
padding: 0 12px;
}
body.mobile .topology-canvas {
width: 100%;
height: 300px;
}
body.mobile .query-log-table {
font-size: 12px;
}
body.mobile .query-log-row {
padding: 10px 12px;
}
/* Hide hover-only elements on touch devices */
body.mobile .hover-only {
display: none;
}
/* Make tabs scrollable horizontally on mobile */
body.mobile .tab-bar {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
white-space: nowrap;
}
body.mobile .tab-bar .tab {
display: inline-block;
min-width: 80px;
}
/* Safe area insets for notched devices */
body.mobile .header {
padding-top: max(12px, env(safe-area-inset-top));
padding-left: max(16px, env(safe-area-inset-left));
padding-right: max(16px, env(safe-area-inset-right));
}
body.mobile .footer {
padding-bottom: max(12px, env(safe-area-inset-bottom));
}
Analysis metadata
Model
qwen3-235b-a22b-2507
Analysed
Jul 24, 2026, 6:28 PM
Modified files
6
Store
firefox
Store ID
ad-bear

Discovery