Package Browser support for testing

This commit is contained in:
Kelvin
2026-01-27 05:13:15 +01:00
parent 8536861e09
commit 5ba2f2be75
2 changed files with 174 additions and 0 deletions
@@ -15,6 +15,7 @@ import com.caoccao.javet.values.primitive.V8ValueString
import com.caoccao.javet.values.reference.IV8ValuePromise import com.caoccao.javet.values.reference.IV8ValuePromise
import com.caoccao.javet.values.reference.V8ValueObject import com.caoccao.javet.values.reference.V8ValueObject
import com.caoccao.javet.values.reference.V8ValuePromise import com.caoccao.javet.values.reference.V8ValuePromise
import com.futo.platformplayer.BuildConfig
import com.futo.platformplayer.api.http.ManagedHttpClient import com.futo.platformplayer.api.http.ManagedHttpClient
import com.futo.platformplayer.api.media.platforms.js.internal.JSHttpClient import com.futo.platformplayer.api.media.platforms.js.internal.JSHttpClient
import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.constructs.Event1
@@ -34,6 +35,7 @@ import com.futo.platformplayer.engine.exceptions.ScriptTimeoutException
import com.futo.platformplayer.engine.exceptions.ScriptUnavailableException import com.futo.platformplayer.engine.exceptions.ScriptUnavailableException
import com.futo.platformplayer.engine.internal.V8Converter import com.futo.platformplayer.engine.internal.V8Converter
import com.futo.platformplayer.engine.packages.PackageBridge import com.futo.platformplayer.engine.packages.PackageBridge
import com.futo.platformplayer.engine.packages.PackageBrowser
import com.futo.platformplayer.engine.packages.PackageDOMParser import com.futo.platformplayer.engine.packages.PackageDOMParser
import com.futo.platformplayer.engine.packages.PackageHttp import com.futo.platformplayer.engine.packages.PackageHttp
import com.futo.platformplayer.engine.packages.PackageHttpImp import com.futo.platformplayer.engine.packages.PackageHttpImp
@@ -387,6 +389,11 @@ class V8Plugin {
"HttpImp" -> PackageHttpImp(this, config) "HttpImp" -> PackageHttpImp(this, config)
"Utilities" -> PackageUtilities(this, config) "Utilities" -> PackageUtilities(this, config)
"JSDOM" -> PackageJSDOM(this, config) "JSDOM" -> PackageJSDOM(this, config)
"Browser" -> {
if(!BuildConfig.DEBUG)
throw IllegalArgumentException("Browser is only allowed for debug builds due to security");
PackageBrowser(this)
};
else -> if(allowNull) null else throw ScriptCompilationException(config, "Unknown package [${packageName}] required for plugin ${config.name}"); else -> if(allowNull) null else throw ScriptCompilationException(config, "Unknown package [${packageName}] required for plugin ${config.name}");
}; };
} }
@@ -0,0 +1,167 @@
package com.futo.platformplayer.engine.packages
import android.graphics.Bitmap
import android.webkit.ConsoleMessage
import android.webkit.JavascriptInterface
import android.webkit.ValueCallback
import android.webkit.WebChromeClient
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.collection.emptyLongSet
import com.caoccao.javet.annotations.V8Function
import com.caoccao.javet.values.reference.V8ValueFunction
import com.futo.platformplayer.engine.V8Plugin
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.states.StateApp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Semaphore
class PackageBrowser: V8Package {
override val name: String get() = "Browser";
override val variableName: String = "browser";
@Transient
private val _readySemaphore = java.util.concurrent.Semaphore(1);
@Transient
private val _callbacks = mutableMapOf<String, (String?)->Unit>();
@Transient
private val _interop = JSInterop(this);
@Transient
private var _browser: WebView? = null;
private val browser: WebView get() {
if(_browser == null)
throw IllegalStateException("Browser not initialized");
return _browser!!;
}
constructor(v8Plugin: V8Plugin): super(v8Plugin) {
}
@V8Function
fun initialize() {
if(_browser == null){
StateApp.instance.scope.launch(Dispatchers.Main) {
_browser = WebView(StateApp.instance.contextOrNull ?: return@launch);
_browser?.settings?.javaScriptEnabled = true;
_browser?.settings?.blockNetworkImage = true;
_browser?.settings?.blockNetworkLoads = true;
_browser?.settings?.allowContentAccess = false;
_browser?.settings?.allowFileAccess = false;
_browser?.webViewClient = object : WebViewClient() {
override fun onPageCommitVisible(view: WebView?, url: String?) {
super.onPageCommitVisible(view, url)
_readySemaphore.release();
Logger.i("PackageBrowser", "Browser loaded");
}
}
_browser?.webChromeClient = object : WebChromeClient() {
override fun onConsoleMessage(consoleMessage: ConsoleMessage?): Boolean {
if(consoleMessage?.messageLevel() == ConsoleMessage.MessageLevel.ERROR)
Logger.e("PackageBrowser", "Console Error:${consoleMessage?.message()} [${consoleMessage?.lineNumber()}]" ?: "");
else
Logger.i("PackageBrowser", "Console Log:" + consoleMessage?.message() ?: "");
return super.onConsoleMessage(consoleMessage)
}
}
_browser?.addJavascriptInterface(_interop, "__GJ");
}
return;
}
}
@V8Function
fun deinitialize() {
_browser = null;
}
@V8Function
fun getCurrentUrl(): String? {
return browser.url;
}
@V8Function
fun waitTillLoaded() {
if(!_readySemaphore.tryAcquire()) {
Logger.i("PackageBrowser", "Waiting for browser to be ready");
_readySemaphore.acquire();
}
_readySemaphore.release();
Logger.i("PackageBrowser", "Browser is ready");
}
@V8Function
fun load(url: String, html: String? = null) {
if(html != null)
Logger.i("PackageBrowser", "Browser loading html with base url [${url}]");
else
Logger.i("PackageBrowser", "Browser loading url [${url}]");
_readySemaphore.acquire();
StateApp.instance.scope.launch(Dispatchers.Main) {
if (html == null)
browser.loadUrl(url);
else
browser.loadDataWithBaseURL(url, html, "text/html", "utf-8", null);
}
}
@V8Function
fun run(js: String, callbackId: String? = null, callback: V8ValueFunction? = null) {
waitTillLoaded();
val funcClone = callback?.toClone<V8ValueFunction>()
if(callbackId != null && callback != null) {
synchronized(_callbacks) {
_callbacks.put(callbackId, {
funcClone?.callVoid(null, arrayOf(it));
});
}
}
StateApp.instance.scope.launch(Dispatchers.Main) {
try {
Logger.i("PackageBrowser", "Browser running JS with callback [${callbackId}]\n${(if(js.length > 200) (js.substring(0, 200) + "...") else js)})");
browser.evaluateJavascript(js, object : ValueCallback<String> {
override fun onReceiveValue(value: String?) {
Logger.i("PackageBrowser", "Browser run finished");
}
})
}
catch(ex: Throwable) {
Logger.e("PackageBrowser", "Failed to invoke browser", ex);
}
}
}
@V8Function
fun runWithReturn(js: String, callback: V8ValueFunction? = null) {
waitTillLoaded();
val funcClone = callback?.toClone<V8ValueFunction>()
StateApp.instance.scope.launch(Dispatchers.Main) {
try {
Logger.i("PackageBrowser", "Browser running JS with callback [sync]\n${(if(js.length > 200) (js.substring(0, 200) + "...") else js)})");
browser.evaluateJavascript(js, object : ValueCallback<String> {
override fun onReceiveValue(value: String?) {
Logger.i("PackageBrowser", "Browser run returned: " + (value ?: ""));
funcClone?.callVoid(null, arrayOf(value));
}
})
}
catch(ex: Throwable) {
Logger.e("PackageBrowser", "Failed to invoke browser", ex);
}
}
}
class JSInterop(private val pack: PackageBrowser) {
@JavascriptInterface
fun callback(id: String, result: String) {
Logger.i("PackageBrowser", "Browser Callback [${id}]: ${result}");
val callback = synchronized(pack._callbacks) { pack._callbacks.remove(id); };
if(callback != null)
callback.invoke(result);
}
@JavascriptInterface
fun log(msg: String) {
Logger.i("PackageBrowser", "Log: " + msg);
}
}
}