I am adding a JavaScript function in WebView like this (Kotlin):
val webView = findViewById(R.id.webview) as WebView
webView.getSettings().setJavaScriptEnabled(true)
webView.addJavascriptInterface(this, "android")
webView.getSettings().setBuiltInZoomControls(false)
webView.loadUrl(url)
webView.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView, url: String) {
super.onPageFinished(view, url)
webView.loadUrl("javascript:(function captchaResponse (token){" +
" android.reCaptchaCallbackInAndroid(token);" +
" })()")
}
}
The function works fine, but the problem is that it runs immediately, when I add it in WebView. I only want to include it as a JavaScript function and it should be called only from the HTML, when the user will fill the reCAPTCHA. How can I do that?
JavaScript is disabled in a WebView by default. You can enable it through the WebSettings attached to your WebView . You can retrieve WebSettings with getSettings() , then enable JavaScript with setJavaScriptEnabled() . WebView myWebView = (WebView) findViewById(R.
You can do this by adding a JavaScript Interface to your WebView and exposing specific methods to the JavaScript code running in your web view. In other words, you'll need to wrap the calls to Android's Toast class in a method you create in your activity/fragment.
WebView allows you to bind JavaScript code to Android code through an interface. To do this, we must use the addJavaScriptInterface() method, which is passed the class that provides the interface for JS, and the name that will be used to display the instance in JS (for example, “AndroidFunction“).
In order to run your reCaptchaCallbackInAndroid
exposed method from JavaScript, when the user submitted a successful reCAPTCHA response, first make sure, to actually listen to the reCAPTCHA callback
via g-recaptcha
tag attributes:
<div class="g-recaptcha"
data-sitekey="{{your site key}}"
data-callback="myCustomJavaScriptCallback"
></div>
or via the reCAPTCHA v2 JavaScript API:
grecaptcha.render(
'g-recaptcha-element-id', {
sitekey: '{{your site key}}',
callback: 'myCustomJavaScriptCallback'
}
)
then, when the page finished loading in the WebView
, add your JavaScript callback function to the window
object using webView.loadUrl
:
webView.loadUrl("""
javascript:(function() {
window.myCustomJavaScriptCallback = function(token) {
android.reCaptchaCallbackInAndroid(token);
}
})()
""".trimIndent())
and finally, when the user submits a successful reCAPTCHA response, your myCustomJavaScriptCallback
will be called and through that, your exposed reCaptchaCallbackInAndroid
method too with the reCAPTCHA token
.
Since you're using Kotlin, in this case, you can just simply use multiline string literals.
Since you're exposing a method to JavaScript, make sure to know the security concerns.
In case you'll need additional JavaScript injection in the future (more method exposure, DOM manipulation, etc.), check out this post.
Set reCAPTCHA to call your captchaResponse
JavaScript function via tag attribute
:
<div class="g-recaptcha"
...
data-callback="captchaResponse"
...
></div>
or via its API
:
grecaptcha.render(
'...', {
...
callback: 'captchaResponse'
...
}
)
and add your captchaResponse
callback function to window
:
webView.loadUrl("""
javascript:(function() {
window.captchaResponse = function(token) {
// your code here before the Android callback...
android.reCaptchaCallbackInAndroid(token);
// ...or after the Android callback
}
})()
""".trimIndent())
Here's a simple, Empty Activity
in Android Studio (using Kotlin) with a basic LinearLayout
(an EditText
and a Button
within the layout) and the MainActivity.kt
:
package com.richrdkng.injectjsintowebview
import android.net.Uri
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.webkit.JavascriptInterface
import kotlinx.android.synthetic.main.activity_main.*
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.Toast
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
sendButton.setOnClickListener { loadWebpage() }
}
@Throws(UnsupportedOperationException::class)
fun buildUri(authority: String) : Uri {
val builder = Uri.Builder()
builder.scheme("https")
.authority(authority)
return builder.build()
}
@JavascriptInterface
fun reCaptchaCallbackInAndroid(token: String) {
val tok = token.substring(0, token.length / 2) + "..."
Toast.makeText(this.applicationContext, tok, Toast.LENGTH_LONG).show()
}
fun loadWebpage() {
webView.getSettings().setJavaScriptEnabled(true)
webView.addJavascriptInterface(this, "android")
webView.getSettings().setBuiltInZoomControls(false)
webView.loadUrl("https://www.richrdkng.com/recaptcha-v2-test/")
webView.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView, url: String) {
super.onPageFinished(view, url)
webView.loadUrl("""
javascript:(function() {
window.onCaptchaSuccess = function(token) {
android.reCaptchaCallbackInAndroid(token);
}
})()
""".trimIndent())
}
}
}
}
then using a simple reCAPTCHA v2 test website, the window.onCaptchaSuccess
function is called upon a successful reCAPTCHA submission and the reCAPTCHA token is partially displayed in a Toast
using an Android Emulator:
Full disclosure: I made the reCAPTCHA v2 test website to prepare/test/debug similar situations.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With