Files
obsidian-vault/work/wiki/apple-browsers/ios-tracker-blocking-implementation.md

924 lines
36 KiB
Markdown

---
source: ~/DuckDuckGo/apple-browsers.git/main/.cursor/rules/ios-tracker-blocking-implementation.mdc
confidence: 0.9
namespace: work
last_synced: 2026-04-28
alwaysApply: false
---
# iOS Tracker Blocking Implementation Guide
## Overview
This document provides a comprehensive overview of how content blocking has been implemented on iOS to protect user privacy and block tracking attempts. Our implementation uses a dual-approach strategy that combines the efficiency of WebKit's native content blocking with the flexibility of JavaScript-based protection.
**Implementation Strategy:**
- **Primary Layer**: Content Blocker Rules (WebKit native, compiled to bytecode)
- **Secondary Layer**: JavaScript injection with Tracker Radar data (gap coverage + surrogates)
## Content Blocker Rules
### Architecture Overview
Content Blocker Rules form the primary layer of our tracker blocking implementation. These rules are converted from our Tracker Radar dataset into Apple's Content Blocker Rules format and compiled by WebKit into efficient bytecode for optimal performance.
**Key Characteristics:**
-**High Performance**: Rules are compiled to bytecode by WebKit
-**Low Memory Footprint**: Optimized for mobile devices
-**Battery Efficient**: Minimal CPU overhead
-**Limited Flexibility**: Cannot support complex logic or surrogates
-**No Runtime Modification**: Rules are static once compiled
### Implementation Details
#### ContentBlockerRulesManager
The `ContentBlockerRulesManager` is responsible for converting Tracker Radar data into Apple's Content Blocker format and managing the rule compilation process.
```swift
import WebKit
import BrowserServicesKit
final class ContentBlockerRulesManager {
private let trackerDataManager: TrackerDataManager
private let compilationQueue = DispatchQueue(label: "content-blocker-compilation", qos: .utility)
init(trackerDataManager: TrackerDataManager) {
self.trackerDataManager = trackerDataManager
}
/// Converts Tracker Radar data to Content Blocker Rules format
func generateContentBlockerRules() async throws -> [WKContentRuleList] {
return try await withCheckedThrowingContinuation { continuation in
compilationQueue.async {
do {
let trackerData = self.trackerDataManager.embeddedTrackerData
let rules = self.convertToContentBlockerFormat(trackerData)
let ruleList = try self.compileRules(rules)
continuation.resume(returning: [ruleList])
} catch {
continuation.resume(throwing: error)
}
}
}
}
/// Converts TrackerData to Apple's Content Blocker Rules JSON format
private func convertToContentBlockerFormat(_ trackerData: TrackerData) -> [[String: Any]] {
var rules: [[String: Any]] = []
for (domain, tracker) in trackerData.trackers {
for rule in tracker.rules ?? [] {
let contentBlockerRule: [String: Any] = [
"trigger": [
"url-filter": rule.rule,
"resource-type": rule.resourceTypes,
"if-domain": rule.whitelist?.compactMap { "*\($0)" }
].compactMapValues { $0 },
"action": [
"type": "block"
]
]
rules.append(contentBlockerRule)
}
}
return rules
}
/// Compiles rules using WebKit's WKContentRuleListStore
private func compileRules(_ rules: [[String: Any]]) throws -> WKContentRuleList {
let jsonData = try JSONSerialization.data(withJSONObject: rules)
let jsonString = String(data: jsonData, encoding: .utf8)!
return try await withCheckedThrowingContinuation { continuation in
WKContentRuleListStore.default().compileContentRuleList(
forIdentifier: "DuckDuckGoContentBlocker",
encodedContentRuleList: jsonString
) { ruleList, error in
if let error = error {
continuation.resume(throwing: error)
} else if let ruleList = ruleList {
continuation.resume(returning: ruleList)
} else {
continuation.resume(throwing: ContentBlockerError.compilationFailed)
}
}
}
}
}
enum ContentBlockerError: Error {
case compilationFailed
case invalidRuleFormat
case trackerDataUnavailable
}
```
#### Integration with WKWebView
Content Blocker Rules are applied to WKWebView through the `WKUserContentController`:
```swift
extension BrowserWebView {
func applyContentBlockerRules() async {
do {
let ruleLists = try await contentBlockerManager.generateContentBlockerRules()
await MainActor.run {
for ruleList in ruleLists {
webView.configuration.userContentController.add(ruleList)
}
Logger.privacy.info("Applied \(ruleLists.count) content blocker rule lists")
}
} catch {
Logger.privacy.error("Failed to apply content blocker rules: \(error)")
// Fallback to JavaScript-only blocking
await applyJavaScriptBlockingOnly()
}
}
}
```
### ContentBlockerRulesUserScript
To understand which resources were blocked by Content Blocker Rules, we inject JavaScript that monitors network requests and infers blocking behavior:
```swift
final class ContentBlockerRulesUserScript {
static let source = """
(function() {
'use strict';
const blockedResources = new Set();
const allowedResources = new Set();
// Monitor XMLHttpRequest
const originalXHROpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
const startTime = Date.now();
this.addEventListener('loadend', function() {
const duration = Date.now() - startTime;
if (this.status === 0 && duration < 10) {
// Likely blocked by content blocker
blockedResources.add(url);
reportBlockedResource(url, 'xhr');
} else {
allowedResources.add(url);
}
});
return originalXHROpen.call(this, method, url, async, user, password);
};
// Monitor Fetch API
const originalFetch = window.fetch;
window.fetch = function(input, init) {
const url = typeof input === 'string' ? input : input.url;
const startTime = Date.now();
return originalFetch.call(this, input, init)
.then(response => {
allowedResources.add(url);
return response;
})
.catch(error => {
const duration = Date.now() - startTime;
if (duration < 10) {
blockedResources.add(url);
reportBlockedResource(url, 'fetch');
}
throw error;
});
};
// Monitor image loading
const originalImageSrc = Object.getOwnPropertyDescriptor(Image.prototype, 'src');
Object.defineProperty(Image.prototype, 'src', {
set: function(value) {
const img = this;
img.addEventListener('error', function() {
if (img.naturalWidth === 0 && img.naturalHeight === 0) {
blockedResources.add(value);
reportBlockedResource(value, 'image');
}
});
img.addEventListener('load', function() {
allowedResources.add(value);
});
return originalImageSrc.set.call(this, value);
},
get: originalImageSrc.get
});
function reportBlockedResource(url, type) {
if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.contentBlocker) {
window.webkit.messageHandlers.contentBlocker.postMessage({
type: 'blocked',
url: url,
resourceType: type,
timestamp: Date.now()
});
}
}
// Report statistics periodically
setInterval(function() {
if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.contentBlocker) {
window.webkit.messageHandlers.contentBlocker.postMessage({
type: 'statistics',
blocked: blockedResources.size,
allowed: allowedResources.size,
timestamp: Date.now()
});
}
}, 5000);
})();
"""
}
```
## JavaScript Injection with Tracker Radar
### Purpose and Advantages
While Content Blocker Rules provide excellent performance, they have limitations that JavaScript injection can address:
**JavaScript Injection Advantages:**
-**Surrogate Support**: Can inject replacement scripts to prevent page breakage
-**Runtime Logic**: Complex blocking decisions based on page context
-**Gap Coverage**: Handles edge cases missed by static rules
-**Real-time Updates**: Can adapt to new tracking techniques
-**Performance Overhead**: Higher CPU and memory usage
-**Battery Impact**: More intensive than native blocking
### ContentBlockerUserScript Implementation
The `ContentBlockerUserScript` implements the full Tracker Radar blocking algorithm in JavaScript:
```swift
final class ContentBlockerUserScript {
private let trackerDataManager: TrackerDataManager
private let surrogateManager: SurrogateManager
init(trackerDataManager: TrackerDataManager, surrogateManager: SurrogateManager) {
self.trackerDataManager = trackerDataManager
self.surrogateManager = surrogateManager
}
var source: String {
return generateTrackerBlockingScript()
}
private func generateTrackerBlockingScript() -> String {
let trackerData = trackerDataManager.embeddedTrackerData
let surrogates = surrogateManager.allSurrogates
return """
(function() {
'use strict';
// Embedded tracker data
const TRACKER_DATA = \(trackerData.jsonString);
const SURROGATES = \(surrogates.jsonString);
class TrackerBlocker {
constructor() {
this.blockedCount = 0;
this.allowedCount = 0;
this.setupInterception();
}
setupInterception() {
this.interceptXMLHttpRequest();
this.interceptFetch();
this.interceptScriptLoading();
this.interceptImageLoading();
this.setupMutationObserver();
}
shouldBlockResource(url, type, initiator) {
try {
const urlObj = new URL(url, window.location.href);
const domain = urlObj.hostname;
// Check if domain is in tracker list
const tracker = TRACKER_DATA.trackers[domain];
if (!tracker) return false;
// Apply tracker rules
if (tracker.rules) {
for (const rule of tracker.rules) {
if (this.matchesRule(url, rule, type)) {
// Check for whitelist exceptions
if (rule.whitelist && this.matchesWhitelist(window.location.hostname, rule.whitelist)) {
return false;
}
return true;
}
}
}
return false;
} catch (error) {
console.warn('Error checking tracker status:', error);
return false;
}
}
matchesRule(url, rule, resourceType) {
// Simple regex matching for rule.rule
try {
const regex = new RegExp(rule.rule, 'i');
if (!regex.test(url)) return false;
// Check resource type restrictions
if (rule.resourceTypes && rule.resourceTypes.length > 0) {
return rule.resourceTypes.includes(resourceType);
}
return true;
} catch (error) {
return false;
}
}
matchesWhitelist(hostname, whitelist) {
return whitelist.some(domain => {
if (domain.startsWith('*.')) {
const suffix = domain.substring(2);
return hostname === suffix || hostname.endsWith('.' + suffix);
}
return hostname === domain;
});
}
getSurrogate(url) {
for (const surrogate of SURROGATES) {
if (surrogate.matches.some(pattern => {
try {
const regex = new RegExp(pattern, 'i');
return regex.test(url);
} catch {
return false;
}
})) {
return surrogate.replacement;
}
}
return null;
}
interceptXMLHttpRequest() {
const originalOpen = XMLHttpRequest.prototype.open;
const self = this;
XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
if (self.shouldBlockResource(url, 'xmlhttprequest', 'script')) {
self.blockedCount++;
self.reportBlocked(url, 'xhr');
// Simulate blocked request
setTimeout(() => {
const event = new Event('error');
this.dispatchEvent(event);
}, 0);
return;
}
self.allowedCount++;
return originalOpen.call(this, method, url, async, user, password);
};
}
interceptFetch() {
const originalFetch = window.fetch;
const self = this;
window.fetch = function(input, init) {
const url = typeof input === 'string' ? input : input.url;
if (self.shouldBlockResource(url, 'xmlhttprequest', 'script')) {
self.blockedCount++;
self.reportBlocked(url, 'fetch');
return Promise.reject(new TypeError('Failed to fetch'));
}
self.allowedCount++;
return originalFetch.call(this, input, init);
};
}
interceptScriptLoading() {
const self = this;
const originalCreateElement = document.createElement;
document.createElement = function(tagName) {
const element = originalCreateElement.call(this, tagName);
if (tagName.toLowerCase() === 'script') {
const originalSrcSetter = Object.getOwnPropertyDescriptor(HTMLScriptElement.prototype, 'src').set;
Object.defineProperty(element, 'src', {
set: function(value) {
if (self.shouldBlockResource(value, 'script', 'document')) {
self.blockedCount++;
self.reportBlocked(value, 'script');
// Check for surrogate
const surrogate = self.getSurrogate(value);
if (surrogate) {
self.injectSurrogate(surrogate);
}
return;
}
self.allowedCount++;
return originalSrcSetter.call(this, value);
},
get: function() {
return this.getAttribute('src');
}
});
}
return element;
};
}
interceptImageLoading() {
const self = this;
const originalImageSrc = Object.getOwnPropertyDescriptor(Image.prototype, 'src');
Object.defineProperty(Image.prototype, 'src', {
set: function(value) {
if (self.shouldBlockResource(value, 'image', 'document')) {
self.blockedCount++;
self.reportBlocked(value, 'image');
return;
}
self.allowedCount++;
return originalImageSrc.set.call(this, value);
},
get: originalImageSrc.get
});
}
setupMutationObserver() {
const self = this;
const observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
mutation.addedNodes.forEach(function(node) {
if (node.nodeType === Node.ELEMENT_NODE) {
self.processNewElement(node);
}
});
});
});
observer.observe(document.body || document.documentElement, {
childList: true,
subtree: true
});
}
processNewElement(element) {
// Check scripts
if (element.tagName === 'SCRIPT' && element.src) {
if (this.shouldBlockResource(element.src, 'script', 'document')) {
this.blockedCount++;
this.reportBlocked(element.src, 'script');
element.remove();
// Inject surrogate if available
const surrogate = this.getSurrogate(element.src);
if (surrogate) {
this.injectSurrogate(surrogate);
}
}
}
// Check images (tracking pixels)
if (element.tagName === 'IMG' && element.src) {
if (this.shouldBlockResource(element.src, 'image', 'document')) {
this.blockedCount++;
this.reportBlocked(element.src, 'image');
element.remove();
}
}
// Check iframes
if (element.tagName === 'IFRAME' && element.src) {
if (this.shouldBlockResource(element.src, 'subdocument', 'document')) {
this.blockedCount++;
this.reportBlocked(element.src, 'iframe');
element.remove();
}
}
}
injectSurrogate(surrogateCode) {
try {
const script = document.createElement('script');
script.textContent = surrogateCode;
script.setAttribute('data-surrogate', 'true');
(document.head || document.documentElement).appendChild(script);
} catch (error) {
console.warn('Failed to inject surrogate:', error);
}
}
reportBlocked(url, type) {
if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.trackerBlocked) {
window.webkit.messageHandlers.trackerBlocked.postMessage({
url: url,
type: type,
timestamp: Date.now()
});
}
}
getStatistics() {
return {
blocked: this.blockedCount,
allowed: this.allowedCount
};
}
}
// Initialize tracker blocker
window.duckduckgoTracker = new TrackerBlocker();
// Report statistics periodically
setInterval(function() {
const stats = window.duckduckgoTracker.getStatistics();
if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.trackerStats) {
window.webkit.messageHandlers.trackerStats.postMessage(stats);
}
}, 10000);
})();
"""
}
}
```
### Surrogate Support
One of the key advantages of JavaScript injection is the ability to provide surrogate scripts that replace blocked trackers to prevent page breakage:
```swift
final class SurrogateManager {
private let surrogates: [Surrogate]
struct Surrogate: Codable {
let name: String
let matches: [String] // Regex patterns
let replacement: String // JavaScript code
}
var allSurrogates: [Surrogate] {
return surrogates
}
init() {
// Load surrogates from embedded data
self.surrogates = loadEmbeddedSurrogates()
}
private func loadEmbeddedSurrogates() -> [Surrogate] {
// Common surrogates for popular tracking libraries
return [
Surrogate(
name: "Google Tag Manager",
matches: ["googletagmanager\\.com/gtm\\.js"],
replacement: """
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'GA_MEASUREMENT_ID', { 'send_page_view': false });
"""
),
Surrogate(
name: "Google Analytics",
matches: ["google-analytics\\.com/analytics\\.js", "googletagmanager\\.com/gtag/js"],
replacement: """
window.ga = window.ga || function() {
(ga.q = ga.q || []).push(arguments);
};
ga.l = +new Date;
ga('create', 'UA-XXXXXXXX-X', 'auto');
ga('send', 'pageview');
"""
),
Surrogate(
name: "Facebook Pixel",
matches: ["connect\\.facebook\\.net/.*?/fbevents\\.js"],
replacement: """
window.fbq = function() {};
window.fbq.push = function() {};
window.fbq.loaded = true;
window.fbq.version = '2.0';
window.fbq.queue = [];
"""
)
]
}
}
```
## Hybrid Integration Strategy
### Layered Protection Approach
The most effective tracker blocking combines both techniques:
1. **Content Blocker Rules** handle the majority of tracking requests efficiently
2. **JavaScript Injection** provides gap coverage and surrogate support
3. **Message Handlers** coordinate between both layers
```swift
extension BrowserViewController: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
switch message.name {
case "trackerBlocked":
handleTrackerBlocked(message.body)
case "trackerStats":
handleTrackerStatistics(message.body)
case "contentBlocker":
handleContentBlockerMessage(message.body)
default:
break
}
}
private func handleTrackerBlocked(_ messageBody: Any) {
guard let data = messageBody as? [String: Any],
let url = data["url"] as? String,
let type = data["type"] as? String else { return }
// Update privacy dashboard
privacyDashboard.recordBlockedTracker(url: url, type: type)
// Update UI indicators
updateTrackerCountIndicator()
Logger.privacy.debug("Blocked tracker: \(url) (\(type))")
}
private func handleTrackerStatistics(_ messageBody: Any) {
guard let stats = messageBody as? [String: Any],
let blocked = stats["blocked"] as? Int,
let allowed = stats["allowed"] as? Int else { return }
privacyDashboard.updateStatistics(blocked: blocked, allowed: allowed)
}
}
```
### Configuration and Initialization
The complete setup combines both blocking techniques:
```swift
extension BrowserWebView {
func setupTrackerBlocking() async {
let configuration = webView.configuration
let userContentController = configuration.userContentController
// 1. Apply Content Blocker Rules
await applyContentBlockerRules()
// 2. Add JavaScript-based blocking
let contentBlockerScript = WKUserScript(
source: ContentBlockerUserScript(
trackerDataManager: trackerDataManager,
surrogateManager: surrogateManager
).source,
injectionTime: .atDocumentStart,
forMainFrameOnly: false
)
userContentController.addUserScript(contentBlockerScript)
// 3. Add inference script for Content Blocker Rules
let inferenceScript = WKUserScript(
source: ContentBlockerRulesUserScript.source,
injectionTime: .atDocumentStart,
forMainFrameOnly: false
)
userContentController.addUserScript(inferenceScript)
// 4. Register message handlers
userContentController.add(self, name: "trackerBlocked")
userContentController.add(self, name: "trackerStats")
userContentController.add(self, name: "contentBlocker")
Logger.privacy.info("Tracker blocking initialized with hybrid approach")
}
}
```
## Why Not Content Blocking Extension?
**Content Blocking Extensions** would apply to every WKWebView in iOS system-wide, but we chose our approach for specific reasons:
### UX Control
- **Manual Activation Required**: Content Blocking Extensions require users to manually enable them in Settings
- **Limited User Guidance**: Difficult to provide contextual help for activation
- **Our Approach**: Complete control over blocking state and user experience
### Surrogate Support
- **Extensions Cannot Inject**: Content Blocking Extensions can only block, not replace with surrogates
- **Page Breakage Risk**: Many sites depend on tracking scripts for functionality
- **Our Approach**: Intelligent replacement prevents site breakage
### Dynamic Configuration
- **Static Rules Only**: Extensions cannot modify behavior based on user preferences
- **No A/B Testing**: Cannot experiment with different blocking strategies
- **Our Approach**: Runtime configuration and experimentation capability
## Performance Considerations
### Memory Usage
```swift
class TrackerBlockingPerformanceMonitor {
private var lastMemoryWarning: Date?
func optimizeForMemoryPressure() {
// Reduce JavaScript blocker complexity under memory pressure
if let lastWarning = lastMemoryWarning,
Date().timeIntervalSince(lastWarning) < 60 {
// Rely more heavily on Content Blocker Rules
disableComplexJavaScriptBlocking()
}
}
func handleMemoryWarning() {
lastMemoryWarning = Date()
// Clear caches
trackerDataManager.clearCache()
surrogateManager.clearCache()
// Temporarily disable non-essential blocking
temporarilyReduceBlockingComplexity()
}
}
```
### Battery Impact Monitoring
```swift
extension BrowserViewController {
func monitorBlockingPerformance() {
// Monitor JavaScript execution time
let startTime = CFAbsoluteTimeGetCurrent()
webView.evaluateJavaScript("window.duckduckgoTracker.getStatistics()") { result, error in
let executionTime = CFAbsoluteTimeGetCurrent() - startTime
if executionTime > 0.1 { // 100ms threshold
Logger.privacy.warning("Tracker blocking JS execution time: \(executionTime)s")
// Consider optimizing or reducing complexity
}
}
}
}
```
## Testing and Debugging
### Unit Testing Content Blocker Rules
```swift
class ContentBlockerRulesTests: XCTestCase {
func testTrackerDataConversion() async throws {
let mockTrackerData = TrackerData.mock
let rulesManager = ContentBlockerRulesManager(trackerDataManager: MockTrackerDataManager(data: mockTrackerData))
let rules = try await rulesManager.generateContentBlockerRules()
XCTAssertFalse(rules.isEmpty)
XCTAssertEqual(rules.count, 1)
}
func testRuleCompilation() async throws {
let simpleRule = [
[
"trigger": [
"url-filter": ".*tracker\\.com.*"
],
"action": [
"type": "block"
]
]
]
let jsonData = try JSONSerialization.data(withJSONObject: simpleRule)
let jsonString = String(data: jsonData, encoding: .utf8)!
let ruleList = try await withCheckedThrowingContinuation { continuation in
WKContentRuleListStore.default().compileContentRuleList(
forIdentifier: "test",
encodedContentRuleList: jsonString
) { ruleList, error in
if let error = error {
continuation.resume(throwing: error)
} else if let ruleList = ruleList {
continuation.resume(returning: ruleList)
} else {
continuation.resume(throwing: ContentBlockerError.compilationFailed)
}
}
}
XCTAssertNotNil(ruleList)
}
}
```
### UI Testing with Tracker Blocking
```swift
class TrackerBlockingUITests: XCTestCase {
func testTrackerCounterUpdates() {
let app = XCUIApplication()
app.launch()
// Navigate to a page with known trackers
app.textFields["urlField"].tap()
app.textFields["urlField"].typeText("https://example.com/page-with-trackers")
app.buttons["Go"].tap()
// Wait for page load and blocking to take effect
sleep(3)
// Check that tracker counter updated
let trackerCounter = app.staticTexts["trackerCount"]
XCTAssertTrue(trackerCounter.exists)
let counterText = trackerCounter.label
XCTAssertTrue(counterText.contains("blocked"))
}
}
```
## Maintenance and Updates
### Tracker Radar Updates
```swift
class TrackerDataUpdateManager {
func checkForUpdates() async {
let latestVersion = try await fetchLatestTrackerDataVersion()
let currentVersion = trackerDataManager.currentVersion
if latestVersion > currentVersion {
await downloadAndApplyUpdate(version: latestVersion)
}
}
private func downloadAndApplyUpdate(version: String) async {
do {
let newTrackerData = try await downloadTrackerData(version: version)
// Validate data integrity
guard validateTrackerData(newTrackerData) else {
throw TrackerDataError.invalidData
}
// Apply update
trackerDataManager.updateData(newTrackerData)
// Regenerate Content Blocker Rules
await regenerateContentBlockerRules()
Logger.privacy.info("Updated tracker data to version \(version)")
} catch {
Logger.privacy.error("Failed to update tracker data: \(error)")
}
}
}
```
## Conclusion
The iOS tracker blocking implementation provides comprehensive protection through a sophisticated dual-layer approach:
- **Content Blocker Rules** deliver high-performance blocking for the majority of tracking attempts
- **JavaScript injection** ensures complete coverage and enables advanced features like surrogates
- **Hybrid coordination** maximizes both performance and protection effectiveness
This architecture gives us complete control over the user experience while maintaining the privacy protection that DuckDuckGo users expect, without the limitations of system-wide Content Blocking Extensions.
**Key Benefits:**
-**Performance**: WebKit-optimized native blocking for most requests
-**Completeness**: JavaScript layer catches edge cases and provides surrogates
-**Control**: Full UX control without requiring manual user configuration
-**Flexibility**: Runtime configuration and A/B testing capabilities
-**Maintenance**: Easy updates and improvements to blocking logic
This implementation forms the foundation of DuckDuckGo's iOS privacy protection, ensuring users browse with confidence knowing their privacy is protected by industry-leading tracker blocking technology.