Skip to content

Commit adec841

Browse files
committed
Merge remote-tracking branch 'upstream/main' into feature/add-proxy-support
# Conflicts: # webview-compose/src/commonMain/kotlin/io/github/kdroidfilter/webview/setting/PlatformWebSettings.kt # webview-compose/src/jvmMain/kotlin/io/github/kdroidfilter/webview/web/WebViewDesktop.kt # wrywebview/Cargo.toml # wrywebview/src/main/kotlin/io/github/kdroidfilter/webview/wry/WryWebViewPanel.kt # wrywebview/src/main/rust/lib.rs
2 parents d7c4d7f + 3cebc43 commit adec841

14 files changed

Lines changed: 557 additions & 300 deletions

File tree

README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,13 @@ Commands:
222222
state.webSettings.customUserAgentString = "MyApp/1.2.3"
223223
```
224224

225+
Desktop note on user-agent:
225226

227+
* applied at creation time
228+
* changing it **recreates** the WebView (debounced)
229+
* JS context/history may be lostd
230+
231+
👉 Set it early.
226232

227233
### Proxy (desktop only)
228234

@@ -235,13 +241,7 @@ state.webSettings.desktopWebSettings.proxyConfig = ProxyConfig.Socks5("proxy.tld
235241

236242
Proxy is only supported on Windows, macOS 14.0+ and Linux.
237243

238-
Desktop note on both user-agent and proxy:
239-
240-
* applied at creation time
241-
* changing it **recreates** the WebView (debounced)
242-
* JS context/history may be lost
243-
244-
👉 Set them early.
244+
**NOTE:** You need to set the proxy **before** the WebView gets created.
245245

246246
---
247247

demo-shared/src/commonMain/kotlin/io/github/kdroidfilter/webview/demo/DemoToolsPanel.kt

Lines changed: 15 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,21 @@
11
package io.github.kdroidfilter.webview.demo
22

3-
import androidx.compose.foundation.layout.*
3+
import androidx.compose.foundation.layout.Arrangement
4+
import androidx.compose.foundation.layout.Column
5+
import androidx.compose.foundation.layout.ColumnScope
6+
import androidx.compose.foundation.layout.ExperimentalLayoutApi
7+
import androidx.compose.foundation.layout.FlowRow
8+
import androidx.compose.foundation.layout.Row
9+
import androidx.compose.foundation.layout.Spacer
10+
import androidx.compose.foundation.layout.fillMaxSize
11+
import androidx.compose.foundation.layout.fillMaxWidth
12+
import androidx.compose.foundation.layout.height
13+
import androidx.compose.foundation.layout.heightIn
14+
import androidx.compose.foundation.layout.padding
15+
import androidx.compose.foundation.layout.width
16+
import androidx.compose.foundation.rememberScrollState
417
import androidx.compose.foundation.lazy.LazyColumn
518
import androidx.compose.foundation.lazy.items
6-
import androidx.compose.foundation.rememberScrollState
719
import androidx.compose.foundation.verticalScroll
820
import androidx.compose.material3.Button
921
import androidx.compose.material3.Card
@@ -19,17 +31,12 @@ import androidx.compose.material3.Switch
1931
import androidx.compose.material3.Text
2032
import androidx.compose.material3.TextButton
2133
import androidx.compose.runtime.Composable
22-
import androidx.compose.runtime.getValue
23-
import androidx.compose.runtime.mutableStateOf
24-
import androidx.compose.runtime.remember
2534
import androidx.compose.runtime.rememberCoroutineScope
26-
import androidx.compose.runtime.setValue
2735
import androidx.compose.ui.Alignment
2836
import androidx.compose.ui.Modifier
2937
import androidx.compose.ui.unit.dp
3038
import composewebview.demo_shared.generated.resources.Res
3139
import io.github.kdroidfilter.webview.cookie.Cookie
32-
import io.github.kdroidfilter.webview.setting.ProxyConfig
3340
import io.github.kdroidfilter.webview.util.KLogSeverity
3441
import io.github.kdroidfilter.webview.web.WebViewNavigator
3542
import io.github.kdroidfilter.webview.web.WebViewState
@@ -80,9 +87,6 @@ internal fun DemoToolsPanel(
8087
onSetLogSeverity: (KLogSeverity) -> Unit,
8188
) {
8289
val scope = rememberCoroutineScope()
83-
val desktopWebSettings = webViewState.webSettings.desktopWebSettings
84-
var proxyType by remember { mutableStateOf("None") }
85-
var proxyText by remember { mutableStateOf(desktopWebSettings.proxyConfig?.toString() ?: "") }
8690
Surface(modifier = modifier, color = MaterialTheme.colorScheme.surface) {
8791
Column(
8892
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp),
@@ -371,29 +375,6 @@ internal fun DemoToolsPanel(
371375
singleLine = true,
372376
label = { Text("Custom User-Agent") },
373377
)
374-
Spacer(Modifier.height(8.dp))
375-
OutlinedTextField(
376-
value = proxyText,
377-
onValueChange = { proxyText = it },
378-
singleLine = true,
379-
label = { Text("Proxy (desktop only)") },
380-
)
381-
FlowRow(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
382-
listOf("None", "HTTP", "SOCKS5").forEach { type ->
383-
FilterChip(
384-
selected = proxyType == type,
385-
onClick = { proxyType = type },
386-
label = { Text(type) },
387-
)
388-
}
389-
}
390-
Button(
391-
onClick = {
392-
desktopWebSettings.proxyConfig = proxyText.ifBlank { null }?.parseProxyConfig(proxyType)
393-
}
394-
){
395-
Text("Set proxy")
396-
}
397378
}
398379

399380
SectionCard(title = "Logs") {
@@ -472,15 +453,4 @@ private fun inlineHtml(): String =
472453
</script>
473454
</body>
474455
</html>
475-
""".trimIndent()
476-
477-
private fun String.parseProxyConfig(proxyType: String): ProxyConfig? {
478-
val host = substringBefore(":").ifBlank { null } ?: return null
479-
val port = substringAfter(":").toIntOrNull() ?: return null
480-
return when (proxyType) {
481-
"None" -> null
482-
"HTTP" -> ProxyConfig.Http(host, port)
483-
"SOCKS5" -> ProxyConfig.Socks5(host, port)
484-
else -> error("Invalid proxy type: $proxyType")
485-
}
486-
}
456+
""".trimIndent()

webview-compose/src/androidMain/kotlin/io/github/kdroidfilter/webview/web/AndroidWebView.kt

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,20 @@ import io.github.kdroidfilter.webview.util.KLogger
88
import kotlinx.coroutines.CoroutineScope
99

1010
internal class AndroidWebView(
11-
override val webView: WebView,
11+
override val nativeWebView: WebView,
1212
override val scope: CoroutineScope,
1313
override val webViewJsBridge: WebViewJsBridge?,
1414
) : IWebView {
1515
init {
1616
initWebView()
1717
}
1818

19-
override fun canGoBack(): Boolean = webView.canGoBack()
19+
override fun canGoBack(): Boolean = nativeWebView.canGoBack()
2020

21-
override fun canGoForward(): Boolean = webView.canGoForward()
21+
override fun canGoForward(): Boolean = nativeWebView.canGoForward()
2222

2323
override fun loadUrl(url: String, additionalHttpHeaders: Map<String, String>) {
24-
webView.loadUrl(url, additionalHttpHeaders)
24+
nativeWebView.loadUrl(url, additionalHttpHeaders)
2525
}
2626

2727
override suspend fun loadHtml(
@@ -32,7 +32,7 @@ internal class AndroidWebView(
3232
historyUrl: String?,
3333
) {
3434
if (html == null) return
35-
webView.loadDataWithBaseURL(baseUrl, html, mimeType, encoding, historyUrl)
35+
nativeWebView.loadDataWithBaseURL(baseUrl, html, mimeType, encoding, historyUrl)
3636
}
3737

3838
override suspend fun loadHtmlFile(fileName: String, readType: WebViewFileReadType) {
@@ -52,34 +52,35 @@ internal class AndroidWebView(
5252
val selected =
5353
candidates.firstOrNull { path ->
5454
try {
55-
webView.context.assets.open(path).close()
55+
nativeWebView.context.assets.open(path).close()
5656
true
5757
} catch (_: Exception) {
5858
false
5959
}
6060
} ?: candidates.first()
6161
val url = "file:///android_asset/$selected"
62-
webView.loadUrl(url)
62+
nativeWebView.loadUrl(url)
6363
KLogger.d(tag = "AndroidWebView") { "loadUrl $url (candidates: ${candidates.joinToString()})" }
6464
}
65-
WebViewFileReadType.COMPOSE_RESOURCE_FILES -> webView.loadUrl(fileName)
65+
66+
WebViewFileReadType.COMPOSE_RESOURCE_FILES -> nativeWebView.loadUrl(fileName)
6667
}
6768
}
6869

69-
override fun goBack() = webView.goBack()
70+
override fun goBack() = nativeWebView.goBack()
7071

71-
override fun goForward() = webView.goForward()
72+
override fun goForward() = nativeWebView.goForward()
7273

73-
override fun reload() = webView.reload()
74+
override fun reload() = nativeWebView.reload()
7475

75-
override fun stopLoading() = webView.stopLoading()
76+
override fun stopLoading() = nativeWebView.stopLoading()
7677

7778
override fun evaluateJavaScript(script: String, callback: ((String) -> Unit)?) {
7879
val androidScript = "javascript:$script"
7980
KLogger.d {
8081
"evaluateJavaScript: $androidScript"
8182
}
82-
webView.evaluateJavascript(androidScript, callback)
83+
nativeWebView.evaluateJavascript(androidScript, callback)
8384
}
8485

8586
override fun injectJsBridge() {
@@ -97,7 +98,7 @@ internal class AndroidWebView(
9798
}
9899

99100
override fun initJsBridge(webViewJsBridge: WebViewJsBridge) {
100-
webView.addJavascriptInterface(this, "androidJsBridge")
101+
nativeWebView.addJavascriptInterface(this, "androidJsBridge")
101102
}
102103

103104
@JavascriptInterface

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

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
package io.github.kdroidfilter.webview.setting
22

3-
import androidx.compose.runtime.getValue
4-
import androidx.compose.runtime.mutableStateOf
5-
import androidx.compose.runtime.setValue
63
import androidx.compose.ui.graphics.Color
74

85
/**
@@ -16,10 +13,17 @@ sealed class PlatformWebSettings {
1613
) : PlatformWebSettings()
1714

1815
data class DesktopWebSettings(
19-
var transparent: Boolean = true
20-
) : PlatformWebSettings() {
21-
var proxyConfig: ProxyConfig? by mutableStateOf(null)
22-
}
16+
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,
25+
var proxyConfig: ProxyConfig? = null,
26+
) : PlatformWebSettings()
2327

2428
data class IOSWebSettings(
2529
var opaque: Boolean = false,

webview-compose/src/commonMain/kotlin/io/github/kdroidfilter/webview/web/IWebView.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ expect class NativeWebView
99
* Platform WebView abstraction.
1010
*/
1111
interface IWebView {
12-
val webView: NativeWebView
12+
val nativeWebView: NativeWebView
1313

1414
val scope: CoroutineScope
1515

webview-compose/src/commonMain/kotlin/io/github/kdroidfilter/webview/web/WebViewState.kt

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
package io.github.kdroidfilter.webview.web
22

3-
import androidx.compose.runtime.Composable
4-
import androidx.compose.runtime.Stable
5-
import androidx.compose.runtime.getValue
6-
import androidx.compose.runtime.mutableStateListOf
7-
import androidx.compose.runtime.mutableStateOf
8-
import androidx.compose.runtime.remember
9-
import androidx.compose.runtime.setValue
3+
import androidx.compose.runtime.*
104
import androidx.compose.runtime.snapshots.SnapshotStateList
115
import io.github.kdroidfilter.webview.cookie.CookieManager
126
import io.github.kdroidfilter.webview.cookie.WebViewCookieManager
@@ -34,10 +28,8 @@ class WebViewState(
3428

3529
val webSettings: WebSettings by mutableStateOf(WebSettings())
3630

37-
internal var webView by mutableStateOf<IWebView?>(null)
38-
39-
val nativeWebView: NativeWebView
40-
get() = webView?.webView ?: error("WebView is not initialized")
31+
var webView: IWebView? by mutableStateOf(null)
32+
internal set
4133

4234
val cookieManager: CookieManager by mutableStateOf(WebViewCookieManager())
4335
}

webview-compose/src/iosMain/kotlin/io/github/kdroidfilter/webview/web/IOSWebView.kt

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,33 +5,23 @@ import io.github.kdroidfilter.webview.jsbridge.WebViewJsBridge
55
import io.github.kdroidfilter.webview.util.KLogger
66
import kotlinx.cinterop.ExperimentalForeignApi
77
import kotlinx.coroutines.CoroutineScope
8-
import platform.Foundation.NSArray
9-
import platform.Foundation.NSBundle
10-
import platform.Foundation.NSDocumentDirectory
11-
import platform.Foundation.NSFileManager
12-
import platform.Foundation.NSMutableURLRequest
13-
import platform.Foundation.NSSearchPathForDirectoriesInDomains
14-
import platform.Foundation.NSString
15-
import platform.Foundation.NSURL
16-
import platform.Foundation.NSUserDomainMask
17-
import platform.Foundation.setValue
18-
import platform.Foundation.stringByDeletingLastPathComponent
8+
import platform.Foundation.*
199
import platform.WebKit.WKWebView
2010

2111
internal const val IOS_JS_BRIDGE_HANDLER_NAME: String = "iosJsBridge"
2212

2313
internal class IOSWebView(
24-
override val webView: WKWebView,
14+
override val nativeWebView: WKWebView,
2515
override val scope: CoroutineScope,
2616
override val webViewJsBridge: WebViewJsBridge?,
2717
) : IWebView {
2818
init {
2919
initWebView()
3020
}
3121

32-
override fun canGoBack(): Boolean = webView.canGoBack
22+
override fun canGoBack(): Boolean = nativeWebView.canGoBack
3323

34-
override fun canGoForward(): Boolean = webView.canGoForward
24+
override fun canGoForward(): Boolean = nativeWebView.canGoForward
3525

3626
override fun loadUrl(url: String, additionalHttpHeaders: Map<String, String>) {
3727
if (url.startsWith("file://")) {
@@ -52,7 +42,7 @@ internal class IOSWebView(
5242
}
5343

5444
if (readAccessURL != null) {
55-
webView.loadFileURL(fileURL, readAccessURL)
45+
nativeWebView.loadFileURL(fileURL, readAccessURL)
5646
return
5747
}
5848
}
@@ -66,7 +56,7 @@ internal class IOSWebView(
6656
forHTTPHeaderField = key,
6757
)
6858
}
69-
webView.loadRequest(request = request)
59+
nativeWebView.loadRequest(request = request)
7060
}
7161

7262
override suspend fun loadHtml(
@@ -77,7 +67,7 @@ internal class IOSWebView(
7767
historyUrl: String?,
7868
) {
7969
if (html == null) return
80-
webView.loadHTMLString(
70+
nativeWebView.loadHTMLString(
8171
string = html,
8272
baseURL = baseUrl?.let { NSURL.URLWithString(it) },
8373
)
@@ -154,7 +144,7 @@ internal class IOSWebView(
154144
return
155145
}
156146

157-
webView.loadFileURL(fileURL, readAccessURL!!)
147+
nativeWebView.loadFileURL(fileURL, readAccessURL!!)
158148
} catch (e: Exception) {
159149
KLogger.e(e, tag = "IOSWebView") { "Error loading HTML file: $fileName (readType: $readType)" }
160150
val errorHtml =
@@ -172,24 +162,24 @@ internal class IOSWebView(
172162
}
173163

174164
override fun goBack() {
175-
webView.goBack()
165+
nativeWebView.goBack()
176166
}
177167

178168
override fun goForward() {
179-
webView.goForward()
169+
nativeWebView.goForward()
180170
}
181171

182172
override fun reload() {
183-
webView.reload()
173+
nativeWebView.reload()
184174
}
185175

186176
override fun stopLoading() {
187-
webView.stopLoading()
177+
nativeWebView.stopLoading()
188178
}
189179

190180
@OptIn(ExperimentalForeignApi::class)
191181
override fun evaluateJavaScript(script: String, callback: ((String) -> Unit)?) {
192-
webView.evaluateJavaScript(script) { result, error ->
182+
nativeWebView.evaluateJavaScript(script) { result, error ->
193183
if (callback == null) return@evaluateJavaScript
194184
if (error != null) {
195185
KLogger.e { "evaluateJavaScript error: $error" }
@@ -217,6 +207,9 @@ internal class IOSWebView(
217207

218208
override fun initJsBridge(webViewJsBridge: WebViewJsBridge) {
219209
val jsMessageHandler = WKJsMessageHandler(webViewJsBridge)
220-
webView.configuration.userContentController.addScriptMessageHandler(jsMessageHandler, IOS_JS_BRIDGE_HANDLER_NAME)
210+
nativeWebView.configuration.userContentController.addScriptMessageHandler(
211+
scriptMessageHandler = jsMessageHandler,
212+
name = IOS_JS_BRIDGE_HANDLER_NAME
213+
)
221214
}
222215
}

0 commit comments

Comments
 (0)