Last week, I went from WakaTime global rank #135 to #2. Not by building some flashy new framework or jumping on the latest AI hype train. I rebuilt Lens Browser's ad blocking system from scratch because the old one sucked. 😤
Let me show you what 78 hours and 49 minutes of focused development looks like. ⚡
💥 The Problem
Lens Browser is a privacy-first mobile browser. Think "open and go"—no accounts, no sync, no tracking. But here's the thing: if you claim to be privacy-first and your ad blocker barely works, you've failed. 😬
My old implementation:
- ❌ ~30-40% blocking rate
- ❌ False positives breaking legitimate content
- ❌ Noticeable performance impact
- ❌ No user control
That's not acceptable. ❌
🏗️ The New Architecture
Three-Layer Defense System 🛡️
// Pseudo-code showing the decision flow
fun shouldBlockRequest(url: String): Boolean {
// Layer 1: Check whitelist
if (whitelistManager.isWhitelisted(url)) return false
// Layer 2: Check manual blocks
if (blockedDomainManager.isBlocked(url)) return true
// Layer 3: Check against 80k rules
if (adBlockerEngine.matches(url)) return true
// Layer 4: JS observer handles DOM-level blocking
return false
}
Layer 1: Connection Interceptor 🔌
Catches requests before they hit the WebView. This is where the magic happens:
override fun shouldInterceptRequest(
view: WebView?,
request: WebResourceRequest?
): WebResourceResponse? {
val url = request?.url?.toString() ?: return null
if (shouldBlockRequest(url)) {
return WebResourceResponse(
"text/plain",
"utf-8",
ByteArrayInputStream("".toByteArray())
)
}
return super.shouldInterceptRequest(view, request)
}
Layer 2: JavaScript Sanitizer 🧹
Even if something slips through (dynamic content, inline scripts), the JS layer catches it:
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (isAdElement(node)) {
window.LensBridge.shouldBlock(node.id, result => {
if (result) node.remove();
});
}
});
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
🔍 The 80,000 Rule Problem
You can't iterate through 80,000 rules for every request. That's O(n) per request—death by a thousand cuts. 💀
My Solution: ✨
class AdBlockerEngine {
private val domainTrie = Trie()
private val urlPatternMap = HashMap<String, Pattern>()
private val bloomFilter = BloomFilter(expectedElements = 80000)
fun matches(url: String): Boolean {
// Fast negative check
if (!bloomFilter.mightContain(url)) return false
// Trie-based domain lookup: O(m) where m = domain length
val domain = extractDomain(url)
if (domainTrie.search(domain)) return true
// Pattern matching for complex rules
return urlPatternMap.values.any { it.matches(url) }
}
}
Performance Results: 📊
- Average request processing: <5ms ⚡
- Memory footprint: ~30MB for 80k rules 💾
- False positive rate: <1% ✅
📊 Real-Time Statistics
Users want to see what's being blocked. I built a live counter: 👀
data class BlockedContent(
val domain: String,
val url: String,
val timestamp: Long,
val type: BlockType
)
object BlockStatistics {
private val blockedItems = mutableListOf<BlockedContent>()
fun addBlocked(item: BlockedContent) {
blockedItems.add(item)
notifyObservers()
}
fun getStats(): Stats {
return Stats(
totalBlocked = blockedItems.size,
uniqueDomains = blockedItems.map { it.domain }.distinct().size,
blocksByType = blockedItems.groupBy { it.type }
)
}
}
Users can now see:
- Total blocked count (updates in real-time)
- List of blocked domains
- Full URLs that were blocked
- When they were blocked
🎯 Key Features Shipped
1. Privacy Mode 🔒
Hides User-Agent, screen resolution, timezone, and other fingerprinting vectors:
webView.settings.apply {
userAgentString = "Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36"
// More obfuscation here
}
2. Trusted Domain Management ✅
Whitelist system because not everything is an ad:
class WhitelistManager {
fun addDomain(domain: String) {
whitelist.add(domain.lowercase())
persistToStorage()
}
fun isWhitelisted(url: String): Boolean {
return whitelist.any { url.contains(it) }
}
}
3. Security Modal Before Load 🚨
Old way: Load page → detect threat → warn user (too late) ❌
New way: Detect threat → show modal → user decides ✅
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
if (threatDetector.isDangerous(url)) {
showSecurityModal(url, threatLevel)
view?.stopLoading()
}
}
4. Blocklist Updates 🔄
Users can refresh the 80k rules without reinstalling:
suspend fun refreshBlocklist() {
val newRules = api.fetchLatestRules()
adBlockerEngine.updateRules(newRules)
notifyUser("Blocklist updated!")
}
🧪 Testing & Results
SuperAdBlockTest.com Results: 📈
- Before: ~35% blocked ❌
- After: 65%+ blocked ✅
Real-World Testing: 🌐
- CNN.com: 47 trackers blocked 🚫
- Reddit.com: 23 ad domains blocked 🚫
- Random blog: 15 tracking scripts blocked 🚫
Performance: ⚡
- Page load time: -15% (faster with ads blocked) 🚀
- Memory usage: +8% (acceptable for 80k rules) 💾
- Battery impact: Negligible 🔋
💡 Lessons Learned
1. Premature Optimization Is Real 🎯
I spent 2 days building a "perfect" rule matching algorithm. Scrapped it. The current one is 95% as good and shipped in 4 hours.
2. Test on Real Sites 🌍
SuperAdBlockTest is great for benchmarking, but real sites (news, e-commerce, social media) show where your blocker actually fails.
3. Users Want Control 🎛️
The #1 feature request: "Let me whitelist this site." Privacy users want agency, not just defaults.
4. Performance Matters More Than Features ⚡
I could add 50 more features, but if the browser is janky, nobody will use it. Every millisecond counts.
🔮 What's Next
This update proves you can build genuinely private software without VC money or user tracking. But I'm not done: 💪
- [ ] Custom blocklist imports
- [ ] Enhanced fingerprint resistance
- [ ] Per-site settings
- [ ] Optional tab management
- [ ] Sync (optional, encrypted, self-hosted)
📲 Try It
Lens Browser is free and will never have ads or tracking. 🙌
📱 Download on Google Play
🧪 Test it on SuperAdBlockTest.com
📊 The Stats
- WakaTime rank: #135 → #2 globally 🔥
- Hours coded: 78h 49m ⏱️
- Daily average: 11h 15m 💪
- Lines changed: Several thousand 💻
- Coffee consumed: Yes ☕
👨💻 For Other Developers
If you're building privacy tools:
- Start with threat models 🎯: What attacks are you preventing?
- Measure everything 📊: Can't improve what you don't measure
- Ship imperfect code 🚀: Iterate fast, fail fast
- Test with real users 🧪: They'll break your assumptions immediately
Building privacy tech is hard. Building usable privacy tech is harder. But it's worth it. 💪
What ad blocker do you use? Drop a comment. 💬👇
P.S. If you're working on similar problems, let's chat. Privacy should be accessible to everyone. 🔒✨
Top comments (0)