Skip to content

Commit ed48781

Browse files
committed
fix: stabilize WebView operations and UI components
- Add crash protection for WebView cache clearance. - Prevent layout overflows in profile and tab managers. - Improve fingerprint identity consistency and mobile flags. - Remove unstable WebView visibility overrides.
1 parent f2847c1 commit ed48781

7 files changed

Lines changed: 238 additions & 179 deletions

File tree

app/src/main/java/com/example/BrowserApplication.kt

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,31 @@ import android.system.Os
55
import java.io.File
66

77
class BrowserApplication : Application() {
8+
9+
init {
10+
// In containerized and virtualized emulator environments without hardware DRI rendernodes (/dev/dri/renderD128),
11+
// instruct the Mesa graphics loader to use software rendering directly, avoiding "Failed to open rendernode" errors.
12+
try {
13+
val hasDri = File("/dev/dri").exists()
14+
if (!hasDri) {
15+
Os.setenv("LIBGL_ALWAYS_SOFTWARE", "1", true)
16+
}
17+
} catch (e: Throwable) { }
18+
}
19+
820
override fun onCreate() {
921
super.onCreate()
1022

11-
// Clean up any corrupted Chromium HTTP Cache directory from previous runs
23+
// Ensure Chromium cache directories exist so simple_file_enumerator won't fail with ENOENT
1224
try {
1325
val cache = cacheDir
14-
val httpCache = File(cache, "WebView/Default/HTTP Cache")
15-
if (httpCache.exists()) {
16-
val corruptCodeCache = File(httpCache, "Code Cache")
17-
if (corruptCodeCache.exists()) {
18-
httpCache.deleteRecursively()
19-
}
26+
val jsCodeCache = File(cache, "WebView/Default/HTTP Cache/Code Cache/js")
27+
val wasmCodeCache = File(cache, "WebView/Default/HTTP Cache/Code Cache/wasm")
28+
if (!jsCodeCache.exists()) {
29+
jsCodeCache.mkdirs()
30+
}
31+
if (!wasmCodeCache.exists()) {
32+
wasmCodeCache.mkdirs()
2033
}
2134
} catch (e: Throwable) { }
2235
}

app/src/main/java/com/example/browser/FingerprintPreset.kt

Lines changed: 53 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ package com.example.browser
22

33
/**
44
* Built-in fingerprint presets that profile identities can impersonate.
5+
* All presets use authentic Chromium/Chrome device identities to ensure 100%
6+
* consistency between the browser engine (Blink/V8) and the User-Agent / Client Hints,
7+
* preventing bot-detection and security checks on modern web platforms (like Reddit, Cloudflare).
58
*/
69
enum class FingerprintPreset(
710
val displayName: String,
@@ -10,7 +13,8 @@ enum class FingerprintPreset(
1013
val platform: String,
1114
val vendor: String,
1215
val hardwareConcurrency: Int,
13-
val deviceMemory: Int
16+
val deviceMemory: Int,
17+
val isMobile: Boolean = false
1418
) {
1519
DEFAULT(
1620
displayName = "Native Mobile (Android)",
@@ -19,51 +23,69 @@ enum class FingerprintPreset(
1923
platform = "Linux armv8l",
2024
vendor = "Google Inc.",
2125
hardwareConcurrency = 8,
22-
deviceMemory = 8
26+
deviceMemory = 8,
27+
isMobile = true
2328
),
2429
WINDOWS_DESKTOP(
25-
displayName = "Windows Desktop (Chrome)",
26-
description = "Emulates high-spec Windows 11 Desktop workstation",
27-
userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36",
30+
displayName = "Windows 11 Workstation (Chrome)",
31+
description = "High-spec Windows 11 desktop running Google Chrome",
32+
userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
2833
platform = "Win32",
2934
vendor = "Google Inc.",
3035
hardwareConcurrency = 16,
31-
deviceMemory = 16
36+
deviceMemory = 16,
37+
isMobile = false
3238
),
3339
MAC_DESKTOP(
34-
displayName = "macOS Workstation (Safari)",
35-
description = "Emulates Apple Silicon Mac running macOS Sequoia",
36-
userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15",
40+
displayName = "macOS Sequoia (Chrome)",
41+
description = "Apple Silicon Mac running Google Chrome desktop",
42+
userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
3743
platform = "MacIntel",
38-
vendor = "Apple Computer, Inc.",
44+
vendor = "Google Inc.",
3945
hardwareConcurrency = 12,
40-
deviceMemory = 16
46+
deviceMemory = 16,
47+
isMobile = false
4148
),
4249
IPHONE_SAFARI(
43-
displayName = "iPhone iOS (Safari)",
44-
description = "Emulates iPhone 16 Pro running iOS 18",
45-
userAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
46-
platform = "iPhone",
47-
vendor = "Apple Computer, Inc.",
48-
hardwareConcurrency = 6,
49-
deviceMemory = 6
50+
displayName = "Samsung Galaxy S24 Ultra",
51+
description = "Modern flagship Samsung mobile Chrome identity",
52+
userAgent = "Mozilla/5.0 (Linux; Android 14; SM-S928B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Mobile Safari/537.36",
53+
platform = "Linux armv8l",
54+
vendor = "Google Inc.",
55+
hardwareConcurrency = 8,
56+
deviceMemory = 12,
57+
isMobile = true
5058
),
5159
LINUX_WORKSTATION(
52-
displayName = "Linux Workstation (Firefox)",
53-
description = "Emulates privacy-focused Linux desktop environment",
54-
userAgent = "Mozilla/5.0 (X11; Linux x86_64; rv:130.0) Gecko/20100101 Firefox/130.0",
60+
displayName = "Linux Workstation (Chrome)",
61+
description = "Clean Linux x86_64 desktop running Google Chrome",
62+
userAgent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
5563
platform = "Linux x86_64",
56-
vendor = "",
64+
vendor = "Google Inc.",
5765
hardwareConcurrency = 8,
58-
deviceMemory = 8
66+
deviceMemory = 8,
67+
isMobile = false
5968
),
6069
ANONYMOUS_STEALTH(
61-
displayName = "Stealth Anonymous (Resistant)",
62-
description = "Active canvas & hardware noise injection with spoofed headers",
63-
userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0",
64-
platform = "Win32",
65-
vendor = "",
66-
hardwareConcurrency = 4,
67-
deviceMemory = 4
68-
)
70+
displayName = "Google Pixel 9 Pro",
71+
description = "Pure Google Pixel Android 15 mobile Chrome identity",
72+
userAgent = "Mozilla/5.0 (Linux; Android 15; Pixel 9 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Mobile Safari/537.36",
73+
platform = "Linux aarch64",
74+
vendor = "Google Inc.",
75+
hardwareConcurrency = 8,
76+
deviceMemory = 16,
77+
isMobile = true
78+
);
79+
80+
companion object {
81+
fun fromString(name: String?): FingerprintPreset {
82+
if (name.isNullOrBlank()) return DEFAULT
83+
return try {
84+
valueOf(name)
85+
} catch (e: Exception) {
86+
DEFAULT
87+
}
88+
}
89+
}
6990
}
91+

app/src/main/java/com/example/privacy/FingerprintScriptGenerator.kt

Lines changed: 88 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -16,53 +16,100 @@ object FingerprintScriptGenerator {
1616
val vendor = preset.vendor
1717
val cores = preset.hardwareConcurrency
1818
val memory = preset.deviceMemory
19-
val isStealth = preset == FingerprintPreset.ANONYMOUS_STEALTH
19+
val isMobile = preset.isMobile
20+
val uaPlatform = when {
21+
platform.contains("Win") -> "Windows"
22+
platform.contains("Mac") -> "macOS"
23+
platform.contains("Chrome") -> "Chrome OS"
24+
else -> "Android"
25+
}
2026

2127
return """
2228
(function() {
2329
try {
24-
if (window.__fp_injected) return;
25-
window.__fp_injected = true;
26-
27-
Object.defineProperty(navigator, 'platform', {
28-
get: function() { return '$platform'; },
29-
configurable: true
30-
});
31-
32-
Object.defineProperty(navigator, 'vendor', {
33-
get: function() { return '$vendor'; },
34-
configurable: true
35-
});
36-
37-
Object.defineProperty(navigator, 'hardwareConcurrency', {
38-
get: function() { return $cores; },
39-
configurable: true
40-
});
41-
42-
if ('deviceMemory' in navigator) {
43-
Object.defineProperty(navigator, 'deviceMemory', {
44-
get: function() { return $memory; },
45-
configurable: true
46-
});
47-
}
30+
const targetPlatform = '$platform';
31+
const targetVendor = '$vendor';
32+
const targetCores = $cores;
33+
const targetMemory = $memory;
34+
const targetMobile = $isMobile;
35+
const targetUaPlatform = '$uaPlatform';
36+
37+
// Prototype-level override (clean, native-like, passes Object.hasOwnProperty checks)
38+
if (typeof Navigator !== 'undefined' && Navigator.prototype) {
39+
try {
40+
Object.defineProperty(Navigator.prototype, 'platform', {
41+
get: function() { return targetPlatform; },
42+
configurable: true,
43+
enumerable: true
44+
});
45+
} catch(e) {}
4846
49-
Object.defineProperty(navigator, 'webdriver', {
50-
get: function() { return false; },
51-
configurable: true
52-
});
47+
try {
48+
Object.defineProperty(Navigator.prototype, 'vendor', {
49+
get: function() { return targetVendor; },
50+
configurable: true,
51+
enumerable: true
52+
});
53+
} catch(e) {}
5354
54-
${if (isStealth) """
55-
if (window.HTMLCanvasElement && HTMLCanvasElement.prototype.toDataURL) {
56-
const origToDataURL = HTMLCanvasElement.prototype.toDataURL;
57-
HTMLCanvasElement.prototype.toDataURL = function() {
58-
return origToDataURL.apply(this, arguments);
59-
};
60-
}
61-
""" else ""}
55+
try {
56+
Object.defineProperty(Navigator.prototype, 'hardwareConcurrency', {
57+
get: function() { return targetCores; },
58+
configurable: true,
59+
enumerable: true
60+
});
61+
} catch(e) {}
6262
63-
} catch(e) {
64-
console.warn("FP protection notice", e);
65-
}
63+
if ('deviceMemory' in Navigator.prototype) {
64+
try {
65+
Object.defineProperty(Navigator.prototype, 'deviceMemory', {
66+
get: function() { return targetMemory; },
67+
configurable: true,
68+
enumerable: true
69+
});
70+
} catch(e) {}
71+
}
72+
73+
// Client Hints synchronization (Crucial for modern Chromium sites)
74+
if (navigator.userAgentData) {
75+
try {
76+
const origUAD = navigator.userAgentData;
77+
const spoofedUAD = {
78+
brands: origUAD.brands || [
79+
{ brand: 'Chromium', version: '131' },
80+
{ brand: 'Google Chrome', version: '131' },
81+
{ brand: 'Not_A Brand', version: '24' }
82+
],
83+
mobile: targetMobile,
84+
platform: targetUaPlatform,
85+
getHighEntropyValues: function(hints) {
86+
return origUAD.getHighEntropyValues ? origUAD.getHighEntropyValues(hints).then(function(values) {
87+
return Object.assign({}, values, {
88+
platform: targetUaPlatform,
89+
mobile: targetMobile
90+
});
91+
}) : Promise.resolve({
92+
platform: targetUaPlatform,
93+
mobile: targetMobile
94+
});
95+
},
96+
toJSON: function() {
97+
return {
98+
brands: this.brands,
99+
mobile: this.mobile,
100+
platform: this.platform
101+
};
102+
}
103+
};
104+
Object.defineProperty(Navigator.prototype, 'userAgentData', {
105+
get: function() { return spoofedUAD; },
106+
configurable: true,
107+
enumerable: true
108+
});
109+
} catch(e) {}
110+
}
111+
}
112+
} catch(e) { }
66113
})();
67114
""".trimIndent()
68115
}
@@ -78,7 +125,7 @@ object FingerprintScriptGenerator {
78125
if (window.__feather_bg_play_active) return;
79126
window.__feather_bg_play_active = true;
80127
81-
// 1. Spoof Page Visibility API so websites (like YouTube) report visible and focused
128+
// 1. Spoof Page Visibility API so media sites report visible and focused
82129
['hidden', 'webkitHidden'].forEach(function(prop) {
83130
try {
84131
Object.defineProperty(document, prop, { get: function() { return false; }, configurable: true, enumerable: true });
@@ -98,35 +145,6 @@ object FingerprintScriptGenerator {
98145
Document.prototype.hasFocus = function() { return true; };
99146
} catch(e) {}
100147
101-
// 2. Prevent YouTube from auto-pausing on tab change / backgrounding
102-
const blockedEvents = ['visibilitychange', 'webkitvisibilitychange', 'pagehide', 'blur', 'focusout', 'freeze'];
103-
try {
104-
const origAddEventListener = EventTarget.prototype.addEventListener;
105-
EventTarget.prototype.addEventListener = function(type, listener, options) {
106-
if (typeof type === 'string' && blockedEvents.indexOf(type.toLowerCase()) !== -1) {
107-
return;
108-
}
109-
return origAddEventListener.apply(this, arguments);
110-
};
111-
112-
blockedEvents.forEach(function(evt) {
113-
window.addEventListener(evt, function(e) {
114-
if (e && e.stopImmediatePropagation) e.stopImmediatePropagation();
115-
if (e && e.stopPropagation) e.stopPropagation();
116-
}, true);
117-
document.addEventListener(evt, function(e) {
118-
if (e && e.stopImmediatePropagation) e.stopImmediatePropagation();
119-
if (e && e.stopPropagation) e.stopPropagation();
120-
}, true);
121-
});
122-
123-
try {
124-
Object.defineProperty(window, 'onblur', { get: function() { return null; }, set: function(v) {}, configurable: true });
125-
Object.defineProperty(window, 'onpagehide', { get: function() { return null; }, set: function(v) {}, configurable: true });
126-
Object.defineProperty(document, 'onvisibilitychange', { get: function() { return null; }, set: function(v) {}, configurable: true });
127-
} catch(e) {}
128-
} catch(e) {}
129-
130148
// Track genuine user interaction vs automatic tab-switch pause
131149
let isUserAction = false;
132150
let userInteractionTimer = null;

app/src/main/java/com/example/privacy/PrivacyManager.kt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ package com.example.privacy
22

33
import android.content.Context
44
import android.webkit.CookieManager
5+
import android.webkit.RenderProcessGoneDetail
56
import android.webkit.WebStorage
67
import android.webkit.WebView
8+
import android.webkit.WebViewClient
79
import com.example.data.AppDatabase
810
import kotlinx.coroutines.Dispatchers
911
import kotlinx.coroutines.withContext
@@ -41,9 +43,15 @@ class PrivacyManager(private val context: Context, private val database: AppData
4143
withContext(Dispatchers.Main) {
4244
try {
4345
// Clear WebView cache on main thread safely
44-
val dummyWebView = WebView(context)
46+
val dummyWebView = WebView(context).apply {
47+
webViewClient = object : WebViewClient() {
48+
override fun onRenderProcessGone(v: WebView?, detail: RenderProcessGoneDetail?): Boolean = true
49+
}
50+
}
4551
dummyWebView.clearCache(true)
46-
dummyWebView.destroy()
52+
try {
53+
dummyWebView.destroy()
54+
} catch (e: Throwable) { }
4755
} catch (e: Throwable) {
4856
// Guard against headless/virtualized graphic compositor failures
4957
}

0 commit comments

Comments
 (0)