Skip to content

Commit 92c228b

Browse files
authored
Merge pull request #27 from zaroxh/main
Add wry WebView configuration options & DevTools support
2 parents 9b0b2a4 + e401a5f commit 92c228b

7 files changed

Lines changed: 396 additions & 148 deletions

File tree

webview-compose/src/commonMain/kotlin/io/github/kdroidfilter/webview/setting/PlatformWebSettings.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ sealed class PlatformWebSettings {
1414

1515
data class DesktopWebSettings(
1616
var transparent: Boolean = true,
17+
var dataDirectory: String? = null,
18+
var initScript: String? = null,
19+
var enableClipboard: Boolean = true,
20+
var enableDevtools: Boolean = false,
21+
var enableNavigationGestures: Boolean = true,
22+
var incognito: Boolean = false,
23+
var autoplayWithoutUserInteraction: Boolean = false,
24+
var focused: Boolean = true,
1725
) : PlatformWebSettings()
1826

1927
data class IOSWebSettings(
Lines changed: 104 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,56 @@
11
package io.github.kdroidfilter.webview.web
22

3-
import androidx.compose.runtime.Composable
4-
import androidx.compose.runtime.DisposableEffect
5-
import androidx.compose.runtime.LaunchedEffect
6-
import androidx.compose.runtime.getValue
7-
import androidx.compose.runtime.key
8-
import androidx.compose.runtime.mutableStateOf
9-
import androidx.compose.runtime.remember
10-
import androidx.compose.runtime.rememberCoroutineScope
11-
import androidx.compose.runtime.rememberUpdatedState
12-
import androidx.compose.runtime.setValue
3+
import androidx.compose.runtime.*
134
import androidx.compose.ui.Modifier
145
import androidx.compose.ui.awt.SwingPanel
6+
import androidx.compose.ui.graphics.Color
7+
import androidx.compose.ui.graphics.toArgb
158
import io.github.kdroidfilter.webview.cookie.WryCookieManager
169
import io.github.kdroidfilter.webview.jsbridge.WebViewJsBridge
1710
import io.github.kdroidfilter.webview.jsbridge.parseJsMessage
1811
import io.github.kdroidfilter.webview.request.WebRequest
1912
import io.github.kdroidfilter.webview.request.WebRequestInterceptResult
13+
import io.github.kdroidfilter.webview.wry.Rgba
2014
import kotlinx.coroutines.delay
2115

2216
actual class WebViewFactoryParam(
2317
val state: WebViewState,
2418
val fileContent: String = "",
25-
val userAgent: String? = null,
2619
)
2720

2821
actual fun defaultWebViewFactory(param: WebViewFactoryParam): NativeWebView =
2922
when (val content = param.state.content) {
30-
is WebContent.Url -> NativeWebView(content.url, param.userAgent ?: param.state.webSettings.customUserAgentString)
31-
else -> NativeWebView("about:blank", param.userAgent ?: param.state.webSettings.customUserAgentString)
23+
is WebContent.Url -> NativeWebView(
24+
initialUrl = content.url,
25+
customUserAgent = param.state.webSettings.customUserAgentString,
26+
dataDirectory = param.state.webSettings.desktopWebSettings.dataDirectory,
27+
supportZoom = param.state.webSettings.supportZoom,
28+
backgroundColor = param.state.webSettings.backgroundColor.toRgba(),
29+
transparent = param.state.webSettings.desktopWebSettings.transparent,
30+
initScript = param.state.webSettings.desktopWebSettings.initScript,
31+
enableClipboard = param.state.webSettings.desktopWebSettings.enableClipboard,
32+
enableDevtools = param.state.webSettings.desktopWebSettings.enableDevtools,
33+
enableNavigationGestures = param.state.webSettings.desktopWebSettings.enableNavigationGestures,
34+
incognito = param.state.webSettings.desktopWebSettings.incognito,
35+
autoplayWithoutUserInteraction = param.state.webSettings.desktopWebSettings.autoplayWithoutUserInteraction,
36+
focused = param.state.webSettings.desktopWebSettings.focused
37+
)
38+
39+
else -> NativeWebView(
40+
initialUrl = "about:blank",
41+
customUserAgent = param.state.webSettings.customUserAgentString,
42+
dataDirectory = param.state.webSettings.desktopWebSettings.dataDirectory,
43+
supportZoom = param.state.webSettings.supportZoom,
44+
backgroundColor = param.state.webSettings.backgroundColor.toRgba(),
45+
transparent = param.state.webSettings.desktopWebSettings.transparent,
46+
initScript = param.state.webSettings.desktopWebSettings.initScript,
47+
enableClipboard = param.state.webSettings.desktopWebSettings.enableClipboard,
48+
enableDevtools = param.state.webSettings.desktopWebSettings.enableDevtools,
49+
enableNavigationGestures = param.state.webSettings.desktopWebSettings.enableNavigationGestures,
50+
incognito = param.state.webSettings.desktopWebSettings.incognito,
51+
autoplayWithoutUserInteraction = param.state.webSettings.desktopWebSettings.autoplayWithoutUserInteraction,
52+
focused = param.state.webSettings.desktopWebSettings.focused
53+
)
3254
}
3355

3456
@Composable
@@ -44,18 +66,25 @@ actual fun ActualWebView(
4466
val currentOnDispose by rememberUpdatedState(onDispose)
4567
val scope = rememberCoroutineScope()
4668

47-
val desiredUserAgent = state.webSettings.customUserAgentString?.trim()?.takeIf { it.isNotEmpty() }
48-
var effectiveUserAgent by remember { mutableStateOf(desiredUserAgent) }
69+
val desiredSettingsKey = state.webSettings.let {
70+
listOf(
71+
it.customUserAgentString?.trim()?.takeIf(String::isNotEmpty),
72+
it.supportZoom,
73+
it.backgroundColor,
74+
)
75+
}
76+
77+
var effectiveSettingsKey by remember { mutableStateOf(desiredSettingsKey) }
4978

50-
LaunchedEffect(desiredUserAgent) {
51-
if (desiredUserAgent == effectiveUserAgent) return@LaunchedEffect
52-
// Wry applies user-agent at creation time, so recreate the webview after a small debounce.
53-
delay(400)
54-
effectiveUserAgent = desiredUserAgent
79+
LaunchedEffect(desiredSettingsKey) {
80+
if (desiredSettingsKey != effectiveSettingsKey) {
81+
delay(400)
82+
effectiveSettingsKey = desiredSettingsKey
83+
}
5584
}
5685

57-
key(effectiveUserAgent) {
58-
val nativeWebView = remember(state, factory) { factory(WebViewFactoryParam(state, userAgent = effectiveUserAgent)) }
86+
key(effectiveSettingsKey) {
87+
val nativeWebView = remember(state, factory) { factory(WebViewFactoryParam(state)) }
5988

6089
val desktopWebView =
6190
remember(nativeWebView, scope, webViewJsBridge) {
@@ -73,52 +102,58 @@ actual fun ActualWebView(
73102
}
74103

75104
// Poll native state (URL/loading/title/nav) and drain IPC messages for JS bridge.
76-
LaunchedEffect(nativeWebView, state, navigator, webViewJsBridge) {
77-
while (true) {
78-
if (!nativeWebView.isReady()) {
79-
if (state.loadingState !is LoadingState.Initializing) {
80-
state.loadingState = LoadingState.Initializing
105+
listOf(nativeWebView, state, navigator, webViewJsBridge).let {
106+
LaunchedEffect(it) {
107+
while (true) {
108+
if (!nativeWebView.isReady()) {
109+
if (state.loadingState !is LoadingState.Initializing) {
110+
state.loadingState = LoadingState.Initializing
111+
}
112+
delay(50)
113+
continue
81114
}
82-
delay(50)
83-
continue
84-
}
85115

86-
val isLoading = nativeWebView.isLoading()
87-
state.loadingState =
88-
if (isLoading) {
89-
val current = state.loadingState
90-
val next =
91-
when (current) {
92-
is LoadingState.Loading -> (current.progress + 0.02f).coerceAtMost(0.9f)
93-
else -> 0.1f
94-
}
95-
LoadingState.Loading(next)
96-
} else {
97-
LoadingState.Finished
116+
val isLoading = nativeWebView.isLoading()
117+
state.loadingState =
118+
if (isLoading) {
119+
val next =
120+
when (val current = state.loadingState) {
121+
is LoadingState.Loading -> (current.progress + 0.02f).coerceAtMost(0.9f)
122+
else -> 0.1f
123+
}
124+
LoadingState.Loading(next)
125+
} else {
126+
LoadingState.Finished
127+
}
128+
129+
val url = nativeWebView.getCurrentUrl()
130+
if (!url.isNullOrBlank()) {
131+
if (!isLoading || state.lastLoadedUrl.isNullOrBlank()) {
132+
state.lastLoadedUrl = url
133+
}
98134
}
99135

100-
val url = nativeWebView.getCurrentUrl()
101-
if (!url.isNullOrBlank()) {
102-
if (!isLoading || state.lastLoadedUrl.isNullOrBlank()) {
103-
state.lastLoadedUrl = url
136+
val title = nativeWebView.getTitle()
137+
if (!title.isNullOrBlank()) {
138+
state.pageTitle = title
104139
}
105-
}
106140

107-
val title = nativeWebView.getTitle()
108-
if (!title.isNullOrBlank()) {
109-
state.pageTitle = title
110-
}
141+
navigator.canGoBack = nativeWebView.canGoBack()
142+
navigator.canGoForward = nativeWebView.canGoForward()
111143

112-
navigator.canGoBack = nativeWebView.canGoBack()
113-
navigator.canGoForward = nativeWebView.canGoForward()
144+
delay(250)
145+
}
146+
}
114147

115-
if (webViewJsBridge != null) {
116-
for (raw in nativeWebView.drainIpcMessages()) {
117-
parseJsMessage(raw)?.let { webViewJsBridge.dispatch(it) }
148+
LaunchedEffect(it) {
149+
while (true) {
150+
if (webViewJsBridge != null) {
151+
for (raw in nativeWebView.drainIpcMessages()) {
152+
parseJsMessage(raw)?.let { webViewJsBridge.dispatch(it) }
153+
}
118154
}
155+
delay(50)
119156
}
120-
121-
delay(250)
122157
}
123158
}
124159

@@ -137,7 +172,8 @@ actual fun ActualWebView(
137172
method = "GET",
138173
)
139174

140-
return@a when (val interceptResult = navigator.requestInterceptor.onInterceptUrlRequest(webRequest, navigator)) {
175+
return@a when (val interceptResult =
176+
navigator.requestInterceptor.onInterceptUrlRequest(webRequest, navigator)) {
141177
WebRequestInterceptResult.Allow -> true
142178

143179
WebRequestInterceptResult.Reject -> false
@@ -174,3 +210,12 @@ actual fun ActualWebView(
174210
}
175211
}
176212
}
213+
214+
private fun Color.toRgba(): Rgba {
215+
val argb: Int = this.toArgb() // 0xAARRGGBB (sRGB)
216+
val a: UByte = ((argb ushr 24) and 0xFF).toUByte()
217+
val r: UByte = ((argb ushr 16) and 0xFF).toUByte()
218+
val g: UByte = ((argb ushr 8) and 0xFF).toUByte()
219+
val b: UByte = (argb and 0xFF).toUByte()
220+
return Rgba(r = r, g = g, b = b, a = a)
221+
}

wrywebview/Cargo.lock

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

wrywebview/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ path = "src/main/rust/lib.rs"
1212
[dependencies]
1313
thiserror = "2.0.11"
1414
uniffi = "0.29.4"
15-
wry = "0.53.5"
15+
wry = { version = "0.54.2", features = ["devtools"] }
1616

1717
[profile.release]
1818
opt-level = "z"

0 commit comments

Comments
 (0)