From 748551af2a76a2cbdf70e9a480f7042fba8e2c64 Mon Sep 17 00:00:00 2001 From: Koen J Date: Fri, 13 Feb 2026 12:20:30 +0100 Subject: [PATCH] Added support for injecting scripts on bootup. --- app/build.gradle | 1 + .../engine/packages/PackageBrowser.kt | 93 +++++++++++++++++++ app/src/stable/assets/sources/mixcloud | 2 +- app/src/stable/assets/sources/youtube | 2 +- app/src/unstable/assets/sources/mixcloud | 2 +- app/src/unstable/assets/sources/youtube | 2 +- dep/futopay | 2 +- 7 files changed, 99 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 626e5658..fd37672e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -206,6 +206,7 @@ dependencies { implementation 'com.google.zxing:core:3.5.3' implementation 'com.journeyapps:zxing-android-embedded:4.3.0' implementation 'com.caverock:androidsvg-aar:1.4' + implementation 'androidx.webkit:webkit:1.15.0' //Protobuf implementation 'com.google.protobuf:protobuf-javalite:4.33.0' diff --git a/app/src/main/java/com/futo/platformplayer/engine/packages/PackageBrowser.kt b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageBrowser.kt index 81f15a62..2d32c6c2 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/packages/PackageBrowser.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageBrowser.kt @@ -1,6 +1,8 @@ package com.futo.platformplayer.engine.packages +import android.annotation.SuppressLint import android.graphics.Bitmap +import android.os.Looper import android.webkit.ConsoleMessage import android.webkit.JavascriptInterface import android.webkit.ValueCallback @@ -9,6 +11,7 @@ import android.webkit.WebResourceRequest import android.webkit.WebView import android.webkit.WebViewClient import androidx.collection.emptyLongSet +import androidx.webkit.ScriptHandler import com.caoccao.javet.annotations.V8Function import com.caoccao.javet.utils.JavetResourceUtils import com.caoccao.javet.values.reference.V8ValueFunction @@ -24,8 +27,13 @@ import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.sync.Semaphore +import androidx.webkit.WebViewCompat +import androidx.webkit.WebViewFeature +import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeout import kotlinx.serialization.json.Json +import java.util.UUID +import java.util.concurrent.ConcurrentHashMap class PackageBrowser: V8Package { override val name: String get() = "Browser"; @@ -33,6 +41,12 @@ class PackageBrowser: V8Package { private val _json = Json { }; + @Transient + private val _pageLoadScriptRefs = ConcurrentHashMap() + + @Transient + private val _pageLoadScriptsFallback = ConcurrentHashMap() + @Transient private var _readySemaphore: Semaphore? = null; @Transient @@ -63,6 +77,18 @@ class PackageBrowser: V8Package { //_browser?.settings?.useWideViewPort = true; //_browser?.settings?.loadWithOverviewMode = true; _browser?.webViewClient = object : WebViewClient() { + override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) { + super.onPageStarted(view, url, favicon) + + if (!WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) { + // Best-effort fallback. Not equivalent, but as early as WebView exposes. + val scripts = _pageLoadScriptsFallback.values.toList() + for (s in scripts) { + try { view?.evaluateJavascript(s, null) } catch (_: Throwable) {} + } + } + } + override fun onPageCommitVisible(view: WebView?, url: String?) { super.onPageCommitVisible(view, url) _readySemaphore?.release(); @@ -217,6 +243,73 @@ class PackageBrowser: V8Package { } } + @V8Function + fun addScriptOnLoad(js: String): String { + require(js.isNotBlank()) { "Script must be non-empty." } + + val id = UUID.randomUUID().toString() + + onMainBlocking { + if (WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) { + val ref = WebViewCompat.addDocumentStartJavaScript(browser, js, setOf("*")) + _pageLoadScriptRefs[id] = ref + } else { + _pageLoadScriptsFallback[id] = js + } + } + + Logger.i("PackageBrowser", "addScriptOnLoad() registered (id=$id)") + return id + } + + @SuppressLint("RequiresFeature") + @V8Function + fun removeScriptOnLoad(identifier: String): Boolean { + if (identifier.isBlank()) return false + + val ref = _pageLoadScriptRefs.remove(identifier) + val removedFallback = _pageLoadScriptsFallback.remove(identifier) != null + + if (ref != null) { + onMainBlocking { + try { ref.remove() } catch (_: Throwable) {} + } + Logger.i("PackageBrowser", "removeScriptOnLoad() removed (id=$identifier)") + return true + } + + if (removedFallback) { + Logger.i("PackageBrowser", "removeScriptOnLoad() removed fallback (id=$identifier)") + return true + } + + return false + } + + @SuppressLint("RequiresFeature") + @V8Function + fun clearScriptsOnLoad() { + val refs = _pageLoadScriptRefs.values.toList() + _pageLoadScriptRefs.clear() + _pageLoadScriptsFallback.clear() + + onMainBlocking { + for (r in refs) { + try { r.remove() } catch (_: Throwable) {} + } + } + + Logger.i("PackageBrowser", "clearScriptsOnLoad() cleared") + } + + private fun onMainBlocking(block: () -> T): T { + return if (Looper.myLooper() == Looper.getMainLooper()) { + block() + } else runBlocking { + withContext(Dispatchers.Main) { block() } + } + } + class JSInterop(private val pack: PackageBrowser) { @JavascriptInterface diff --git a/app/src/stable/assets/sources/mixcloud b/app/src/stable/assets/sources/mixcloud index 0bbe4c63..1b801553 160000 --- a/app/src/stable/assets/sources/mixcloud +++ b/app/src/stable/assets/sources/mixcloud @@ -1 +1 @@ -Subproject commit 0bbe4c63f42f8bcb889d2e81840351fd7579c2ef +Subproject commit 1b801553b308ace43af07f444090927b0fe58eb1 diff --git a/app/src/stable/assets/sources/youtube b/app/src/stable/assets/sources/youtube index b9aae557..ffd1b535 160000 --- a/app/src/stable/assets/sources/youtube +++ b/app/src/stable/assets/sources/youtube @@ -1 +1 @@ -Subproject commit b9aae557fd5faaff4cbac2108af403e7416d01c3 +Subproject commit ffd1b535d0590480d94c2cc0f526f1c12d36fe41 diff --git a/app/src/unstable/assets/sources/mixcloud b/app/src/unstable/assets/sources/mixcloud index 0bbe4c63..1b801553 160000 --- a/app/src/unstable/assets/sources/mixcloud +++ b/app/src/unstable/assets/sources/mixcloud @@ -1 +1 @@ -Subproject commit 0bbe4c63f42f8bcb889d2e81840351fd7579c2ef +Subproject commit 1b801553b308ace43af07f444090927b0fe58eb1 diff --git a/app/src/unstable/assets/sources/youtube b/app/src/unstable/assets/sources/youtube index b9aae557..ffd1b535 160000 --- a/app/src/unstable/assets/sources/youtube +++ b/app/src/unstable/assets/sources/youtube @@ -1 +1 @@ -Subproject commit b9aae557fd5faaff4cbac2108af403e7416d01c3 +Subproject commit ffd1b535d0590480d94c2cc0f526f1c12d36fe41 diff --git a/dep/futopay b/dep/futopay index 77face99..39965828 160000 --- a/dep/futopay +++ b/dep/futopay @@ -1 +1 @@ -Subproject commit 77face99a8edc1c83a6645cb2f15119f5d626267 +Subproject commit 399658285221c74134c749948ce7e4e2bc2874cf