diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 84a0b92f..662d3ef5 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ +#Sat Nov 04 18:43:10 CET 2023 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/sample/shared/src/commonMain/kotlin/com/kevinnzou/sample/BasicWebViewSample.kt b/sample/shared/src/commonMain/kotlin/com/kevinnzou/sample/BasicWebViewSample.kt index f894e4ba..7f453107 100644 --- a/sample/shared/src/commonMain/kotlin/com/kevinnzou/sample/BasicWebViewSample.kt +++ b/sample/shared/src/commonMain/kotlin/com/kevinnzou/sample/BasicWebViewSample.kt @@ -19,6 +19,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.Close import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -31,11 +32,15 @@ import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.unit.dp import co.touchlab.kermit.Logger import com.multiplatform.webview.cookie.Cookie +import com.multiplatform.webview.request.RequestInterceptor +import com.multiplatform.webview.request.RequestResult import com.multiplatform.webview.util.KLogSeverity +import com.multiplatform.webview.web.IWebView import com.multiplatform.webview.web.LoadingState import com.multiplatform.webview.web.WebView import com.multiplatform.webview.web.rememberWebViewNavigator import com.multiplatform.webview.web.rememberWebViewState +import kotlinx.coroutines.delay /** * Created By Kevin Zou On 2023/9/8 @@ -50,6 +55,22 @@ internal fun BasicWebViewSample() { "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/625.20 (KHTML, like Gecko) Version/14.3.43 Safari/625.20" } val navigator = rememberWebViewNavigator() + + DisposableEffect(Unit) { + + navigator.requestInterceptor = RequestInterceptor { data -> + println("Triggering req interceptor for ${data.url}") + + if (data.isForMainFrame && !data.url.contains(".com")) + RequestResult.Modify(url = "https://request.urih.com/", mapOf("Authorizations" to "true")) // https://request.urih.com/ + else + RequestResult.Allow + } + + onDispose { } + } + + var textFieldValue by remember(state.lastLoadedUrl) { mutableStateOf(state.lastLoadedUrl) } @@ -140,4 +161,4 @@ internal fun BasicWebViewSample() { ) } } -} +} \ No newline at end of file diff --git a/webview/src/androidMain/kotlin/com/multiplatform/webview/web/AccompanistWebView.kt b/webview/src/androidMain/kotlin/com/multiplatform/webview/web/AccompanistWebView.kt index 410a0d7e..b641aaa4 100644 --- a/webview/src/androidMain/kotlin/com/multiplatform/webview/web/AccompanistWebView.kt +++ b/webview/src/androidMain/kotlin/com/multiplatform/webview/web/AccompanistWebView.kt @@ -2,11 +2,13 @@ package com.multiplatform.webview.web import android.content.Context import android.graphics.Bitmap +import android.net.Uri import android.os.Build import android.view.ViewGroup import android.webkit.WebChromeClient import android.webkit.WebResourceError import android.webkit.WebResourceRequest +import android.webkit.WebResourceResponse import android.webkit.WebView import android.webkit.WebViewClient import android.widget.FrameLayout @@ -16,6 +18,8 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.viewinterop.AndroidView +import com.multiplatform.webview.request.RequestData +import com.multiplatform.webview.request.RequestResult import com.multiplatform.webview.util.KLogger /** @@ -209,6 +213,29 @@ open class AccompanistWebViewClient : WebViewClient() { open lateinit var navigator: WebViewNavigator internal set + override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest): Boolean { + val data = RequestData( + url = request.url.toString(), + isForMainFrame = request.isForMainFrame, + isRedirect = request.isRedirect, + method = request.method, + requestHeaders = request.requestHeaders ?: emptyMap() + ) + + KLogger.d { "shouldOverrideUrlLoading: $data" } + val result = navigator.requestInterceptor(data) + KLogger.d { "shouldOverrideUrlLoading: load new: $result" } + + return when (result) { + RequestResult.Allow -> false + is RequestResult.Modify -> { + navigator.loadUrl(result.url, result.additionalHeaders) + true + } + RequestResult.Reject -> true + } + } + override fun onPageStarted( view: WebView, url: String?, diff --git a/webview/src/commonMain/kotlin/com/multiplatform/webview/request/RequestData.kt b/webview/src/commonMain/kotlin/com/multiplatform/webview/request/RequestData.kt new file mode 100644 index 00000000..4d3a8287 --- /dev/null +++ b/webview/src/commonMain/kotlin/com/multiplatform/webview/request/RequestData.kt @@ -0,0 +1,11 @@ +package com.multiplatform.webview.request + + +data class RequestData ( + val url: String, + val isForMainFrame: Boolean, + val isRedirect: Boolean, + + val method: String, + val requestHeaders: Map +) diff --git a/webview/src/commonMain/kotlin/com/multiplatform/webview/request/RequestInterceptor.kt b/webview/src/commonMain/kotlin/com/multiplatform/webview/request/RequestInterceptor.kt new file mode 100644 index 00000000..cd0c668c --- /dev/null +++ b/webview/src/commonMain/kotlin/com/multiplatform/webview/request/RequestInterceptor.kt @@ -0,0 +1,5 @@ +package com.multiplatform.webview.request + +fun interface RequestInterceptor { + operator fun invoke(data: RequestData): RequestResult +} \ No newline at end of file diff --git a/webview/src/commonMain/kotlin/com/multiplatform/webview/request/RequestResult.kt b/webview/src/commonMain/kotlin/com/multiplatform/webview/request/RequestResult.kt new file mode 100644 index 00000000..b4e429bc --- /dev/null +++ b/webview/src/commonMain/kotlin/com/multiplatform/webview/request/RequestResult.kt @@ -0,0 +1,7 @@ +package com.multiplatform.webview.request + +sealed interface RequestResult { + data object Allow : RequestResult + data object Reject : RequestResult + data class Modify(val url: String, val additionalHeaders: Map = emptyMap()) : RequestResult +} diff --git a/webview/src/commonMain/kotlin/com/multiplatform/webview/web/WebView.kt b/webview/src/commonMain/kotlin/com/multiplatform/webview/web/WebView.kt index 0b713757..b3b94dee 100644 --- a/webview/src/commonMain/kotlin/com/multiplatform/webview/web/WebView.kt +++ b/webview/src/commonMain/kotlin/com/multiplatform/webview/web/WebView.kt @@ -4,6 +4,8 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Modifier +import com.multiplatform.webview.util.KLogger +import kotlinx.coroutines.delay import org.jetbrains.compose.resources.ExperimentalResourceApi /** @@ -34,9 +36,16 @@ fun WebView( onCreated: () -> Unit = {}, onDispose: () -> Unit = {}, ) { - val webView = state.webView + ActualWebView( + state = state, + modifier = modifier, + captureBackPresses = captureBackPresses, + navigator = navigator, + onCreated = onCreated, + onDispose = onDispose, + ) - webView?.let { wv -> + state.webView?.let { wv -> LaunchedEffect(wv, navigator) { with(navigator) { wv.handleNavigationEvents() @@ -80,14 +89,7 @@ fun WebView( } } - ActualWebView( - state = state, - modifier = modifier, - captureBackPresses = captureBackPresses, - navigator = navigator, - onCreated = onCreated, - onDispose = onDispose, - ) + } /** diff --git a/webview/src/commonMain/kotlin/com/multiplatform/webview/web/WebViewNavigator.kt b/webview/src/commonMain/kotlin/com/multiplatform/webview/web/WebViewNavigator.kt index ede496cc..9fd6b0c2 100644 --- a/webview/src/commonMain/kotlin/com/multiplatform/webview/web/WebViewNavigator.kt +++ b/webview/src/commonMain/kotlin/com/multiplatform/webview/web/WebViewNavigator.kt @@ -7,8 +7,12 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue +import com.multiplatform.webview.request.RequestInterceptor +import com.multiplatform.webview.request.RequestResult +import com.multiplatform.webview.util.KLogger import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -121,6 +125,7 @@ class WebViewNavigator(private val coroutineScope: CoroutineScope) { internal suspend fun IWebView.handleNavigationEvents(): Nothing = withContext(Dispatchers.Main) { navigationEvents.collect { event -> + KLogger.d { "collecting event $event" } when (event) { is NavigationEvent.Back -> goBack() is NavigationEvent.Forward -> goForward() @@ -175,7 +180,9 @@ class WebViewNavigator(private val coroutineScope: CoroutineScope) { url: String, additionalHttpHeaders: Map = emptyMap(), ) { + KLogger.d { "loadUrl with $url $additionalHttpHeaders" } coroutineScope.launch { + KLogger.d { "loadUrl emitting with $url $additionalHttpHeaders" } navigationEvents.emit( NavigationEvent.LoadUrl( url, @@ -291,6 +298,9 @@ class WebViewNavigator(private val coroutineScope: CoroutineScope) { fun stopLoading() { coroutineScope.launch { navigationEvents.emit(NavigationEvent.StopLoading) } } + + + var requestInterceptor: RequestInterceptor by mutableStateOf(RequestInterceptor { _ -> RequestResult.Allow }) } /** diff --git a/webview/src/desktopMain/kotlin/com/multiplatform/webview/web/WebEngineExt.kt b/webview/src/desktopMain/kotlin/com/multiplatform/webview/web/WebEngineExt.kt index a1b1fa94..02d59397 100644 --- a/webview/src/desktopMain/kotlin/com/multiplatform/webview/web/WebEngineExt.kt +++ b/webview/src/desktopMain/kotlin/com/multiplatform/webview/web/WebEngineExt.kt @@ -1,12 +1,28 @@ package com.multiplatform.webview.web +import com.multiplatform.webview.request.RequestData +import com.multiplatform.webview.request.RequestResult import com.multiplatform.webview.util.KLogger +import dev.datlag.kcef.KCEFBrowser import org.cef.CefSettings import org.cef.browser.CefBrowser import org.cef.browser.CefFrame +import org.cef.callback.CefAuthCallback +import org.cef.callback.CefCallback +import org.cef.handler.CefCookieAccessFilter import org.cef.handler.CefDisplayHandler import org.cef.handler.CefLoadHandler +import org.cef.handler.CefRequestHandler +import org.cef.handler.CefRequestHandlerAdapter +import org.cef.handler.CefResourceHandler +import org.cef.handler.CefResourceRequestHandler +import org.cef.handler.CefResourceRequestHandlerAdapter +import org.cef.misc.BoolRef +import org.cef.misc.StringRef import org.cef.network.CefRequest +import org.cef.network.CefResponse +import org.cef.network.CefURLRequest +import org.cef.security.CefSSLInfo /** * Created By Kevin Zou On 2023/9/12 @@ -66,10 +82,66 @@ internal fun CefBrowser.addDisplayHandler(state: WebViewState) { ) } +internal fun KCEFBrowser.addRequestHandler( + state: WebViewState, + navigator: WebViewNavigator +) { + + client.addRequestHandler(object : CefRequestHandlerAdapter() { + override fun getResourceRequestHandler( + browser: CefBrowser, + frame: CefFrame, + request: CefRequest, + isNavigation: Boolean, + isDownload: Boolean, + requestInitiator: String, + disableDefaultHandling: BoolRef + ) = object : CefResourceRequestHandlerAdapter() { + override fun onBeforeResourceLoad( + browser: CefBrowser, + frame: CefFrame, + request: CefRequest + ): Boolean { + val data = RequestData( + url = request.url.toString(), + isForMainFrame = frame.isMain, + isRedirect = false, + method = request.method, + requestHeaders = mutableMapOf().also { + request.getHeaderMap( + it + ) + } + ) + + val result = + if (request.resourceType == CefRequest.ResourceType.RT_MAIN_FRAME) navigator.requestInterceptor( + data + ) else return false + KLogger.d { "shouldOverrideUrlLoading: load new: $result" } + + return when (result) { + RequestResult.Allow -> false + is RequestResult.Modify -> { + KLogger.d { "State is ${state.webView}" } + + request.url = result.url + request.setHeaderMap(result.additionalHeaders) + false + } + + RequestResult.Reject -> true + } + } + }.also { KLogger.d { "Created a handler" } } + }) +} + internal fun CefBrowser.addLoadListener( state: WebViewState, navigator: WebViewNavigator, ) { + this.client.addLoadHandler( object : CefLoadHandler { override fun onLoadingStateChange( @@ -117,7 +189,7 @@ internal fun CefBrowser.addLoadListener( ) { state.loadingState = LoadingState.Finished KLogger.e { - "Failed to load url: ${failedUrl}\n$errorText" + "Failed to load url: ${failedUrl} $errorText $errorCode $failedUrl" } state.errorsForCurrentRequest.add( WebViewError( diff --git a/webview/src/desktopMain/kotlin/com/multiplatform/webview/web/WebView.desktop.kt b/webview/src/desktopMain/kotlin/com/multiplatform/webview/web/WebView.desktop.kt index c67a983f..d84bb9eb 100644 --- a/webview/src/desktopMain/kotlin/com/multiplatform/webview/web/WebView.desktop.kt +++ b/webview/src/desktopMain/kotlin/com/multiplatform/webview/web/WebView.desktop.kt @@ -8,6 +8,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Modifier import androidx.compose.ui.awt.SwingPanel +import com.multiplatform.webview.util.KLogger import dev.datlag.kcef.KCEF import dev.datlag.kcef.KCEFBrowser import org.cef.browser.CefRendering @@ -61,6 +62,8 @@ fun DesktopWebView( val browser: KCEFBrowser? = remember(client, state.webSettings.desktopWebSettings, fileContent) { + KLogger.d { "Trying to create a webview now... because $client" } + val rendering = if (state.webSettings.desktopWebSettings.offScreenRendering) { CefRendering.OFFSCREEN @@ -68,7 +71,7 @@ fun DesktopWebView( CefRendering.DEFAULT } - when (val current = state.content) { + val view = when (val current = state.content) { is WebContent.Url -> client?.createBrowser( current.url, @@ -100,8 +103,14 @@ fun DesktopWebView( ) } } + + KLogger.d { "View is $view" } + + view }?.also { - state.webView = DesktopWebView(it) + val ww = DesktopWebView(it) + KLogger.d { "Webview is $ww" } + state.webView = ww } browser?.let { @@ -110,6 +119,7 @@ fun DesktopWebView( browser.apply { addDisplayHandler(state) addLoadListener(state, navigator) + addRequestHandler(state, navigator) } onCreated() browser.uiComponent