Implement WebView via Playwright (#1434)
* Implement Android's Looper Looper handles thread messaging. This is used by extensions when they want to enqueue actions e.g. for sleeping while WebView does someting * Stub WebView * Continue stubbing ViewGroup for WebView * Implement WebView via Playwright * Lint * Implement request interception Supports Yidan * Support WebChromeClient For Bokugen * Fix onPageStarted * Make Playwright configurable * Subscribe to config changes * Fix exposing of functions * Support data urls * Looper: Fix infinite sleep * Looper: Avoid killing the loop on exception Just log it and continue * Pump playwright's message queue periodically https://playwright.dev/java/docs/multithreading#pagewaitfortimeout-vs-threadsleep * Update server/src/main/kotlin/suwayomi/tachidesk/graphql/types/SettingsType.kt Co-authored-by: Mitchell Syer <Syer10@users.noreply.github.com> * Stub a KCef WebViewProvider * Initial Kcef Webview implementation Still buggy, on the second call it just seems to fall over * Format, restructure to create browser on load This is much more consistent, before we would sometimes see errors from about:blank, which block the actual page * Implement some small useful properties * Move inline objects to class * Handle requests in Kcef * Move Playwright implementation * Document Playwright settings, fix deprecated warnings * Inject default user agent from NetworkHelper * Move playwright to libs.versions.toml * Lint * Fix missing imports after lint * Update server/src/main/kotlin/suwayomi/tachidesk/server/ServerSetup.kt Co-authored-by: Mitchell Syer <Syer10@users.noreply.github.com> * Fix default user agent set/get Use System.getProperty instead of SystemProperties.get * Configurable WebView provider implementation * Simplify Playwright settings init * Minor cleanup and improvements * Remove playwright WebView impl * Document WebView for Linux --------- Co-authored-by: Mitchell Syer <Syer10@users.noreply.github.com>
This commit is contained in:
@@ -16,6 +16,7 @@ import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
@@ -54,6 +55,7 @@ class NetworkHelper(
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
|
||||
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
||||
)
|
||||
val userAgentFlow = userAgent.asStateFlow()
|
||||
|
||||
fun defaultUserAgentProvider(): String = userAgent.value
|
||||
|
||||
|
||||
@@ -117,6 +117,14 @@ class ServerConfig(
|
||||
// extensions
|
||||
val extensionRepos: MutableStateFlow<List<String>> by OverrideConfigValues(StringConfigAdapter)
|
||||
|
||||
// playwright webview
|
||||
val playwrightBrowser: MutableStateFlow<String> by OverrideConfigValue(StringConfigAdapter)
|
||||
val playwrightWsEndpoint: MutableStateFlow<String> by OverrideConfigValue(StringConfigAdapter)
|
||||
val playwrightSandbox: MutableStateFlow<Boolean> by OverrideConfigValue(BooleanConfigAdapter)
|
||||
|
||||
// webview
|
||||
val webviewImpl: MutableStateFlow<String> by OverrideConfigValue(StringConfigAdapter)
|
||||
|
||||
// requests
|
||||
val maxSourcesInParallel: MutableStateFlow<Int> by OverrideConfigValue(IntConfigAdapter)
|
||||
|
||||
|
||||
@@ -7,8 +7,10 @@ package suwayomi.tachidesk.server
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import android.os.Looper
|
||||
import ch.qos.logback.classic.Level
|
||||
import com.typesafe.config.ConfigRenderOptions
|
||||
import dev.datlag.kcef.KCEF
|
||||
import eu.kanade.tachiyomi.App
|
||||
import eu.kanade.tachiyomi.createAppModule
|
||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
@@ -16,9 +18,14 @@ import eu.kanade.tachiyomi.source.local.LocalSource
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import io.javalin.json.JavalinJackson
|
||||
import io.javalin.json.JsonMapper
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||
import org.koin.core.context.startKoin
|
||||
import org.koin.core.module.Module
|
||||
@@ -50,6 +57,10 @@ import java.net.Authenticator
|
||||
import java.net.PasswordAuthentication
|
||||
import java.security.Security
|
||||
import java.util.Locale
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.createDirectories
|
||||
import kotlin.io.path.div
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
private val logger = KotlinLogging.logger {}
|
||||
|
||||
@@ -70,6 +81,15 @@ class ApplicationDirs(
|
||||
val mangaDownloadsRoot get() = "$downloadsRoot/mangas"
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
class LooperThread : Thread() {
|
||||
override fun run() {
|
||||
logger.info { "Starting Android Main Loop" }
|
||||
Looper.prepareMainLooper()
|
||||
Looper.loop()
|
||||
}
|
||||
}
|
||||
|
||||
data class ProxySettings(
|
||||
val proxyEnabled: Boolean,
|
||||
val socksProxyVersion: Int,
|
||||
@@ -100,11 +120,15 @@ fun serverModule(applicationDirs: ApplicationDirs): Module =
|
||||
single<JsonMapper> { JavalinJackson() }
|
||||
}
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
fun applicationSetup() {
|
||||
Thread.setDefaultUncaughtExceptionHandler { _, throwable ->
|
||||
KotlinLogging.logger { }.error(throwable) { "unhandled exception" }
|
||||
}
|
||||
|
||||
val mainLoop = LooperThread()
|
||||
mainLoop.start()
|
||||
|
||||
// register Tachidesk's config which is dubbed "ServerConfig"
|
||||
GlobalConfigManager.registerModule(
|
||||
ServerConfig.register { GlobalConfigManager.config },
|
||||
@@ -187,7 +211,12 @@ fun applicationSetup() {
|
||||
androidCompat.startApp(app)
|
||||
|
||||
// Initialize NetworkHelper early
|
||||
Injekt.get<NetworkHelper>()
|
||||
Injekt
|
||||
.get<NetworkHelper>()
|
||||
.userAgentFlow
|
||||
.onEach {
|
||||
System.setProperty("http.agent", it)
|
||||
}.launchIn(GlobalScope)
|
||||
|
||||
// create or update conf file if doesn't exist
|
||||
try {
|
||||
@@ -312,4 +341,29 @@ fun applicationSetup() {
|
||||
|
||||
// start DownloadManager and restore + resume downloads
|
||||
DownloadManager.restoreAndResumeDownloads()
|
||||
|
||||
GlobalScope.launch {
|
||||
val logger = KotlinLogging.logger("KCEF")
|
||||
KCEF.init(
|
||||
builder = {
|
||||
progress {
|
||||
var lastNum = -1
|
||||
onDownloading {
|
||||
val num = it.roundToInt()
|
||||
if (num > lastNum) {
|
||||
lastNum = num
|
||||
logger.info { "KCEF download progress: $num%" }
|
||||
}
|
||||
}
|
||||
}
|
||||
download { github() }
|
||||
val kcefDir = Path(applicationDirs.dataRoot) / "bin/kcef"
|
||||
kcefDir.createDirectories()
|
||||
installDir(kcefDir.toFile())
|
||||
},
|
||||
onError = {
|
||||
it?.printStackTrace()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user