12

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?

8
  • Do you need to load the javascript function dynamically? Else you can load it as part of the html itself using script tag inside head in html. Commented Jul 29, 2018 at 11:44
  • I have to load it dynamically @SaikrishnaRajaraman
    – Zohab Ali
    Commented Jul 30, 2018 at 9:27
  • Try reference the javascript files inside the html. Commented Jul 31, 2018 at 7:18
  • Is your HTML is a local file or coming from server? Commented Jul 31, 2018 at 7:29
  • see here developer.android.com/guide/webapps/webview Commented Jul 31, 2018 at 8:17

2 Answers 2

10
+500

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.


In your case:

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())

Test:

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:

virtual machine with successful reCAPTCHA v2


Full disclosure: I made the reCAPTCHA v2 test website to prepare/test/debug similar situations.

4
  • I cannot just use webView.loadUrl to push javascript code. Problem is the javascript code that I am pushing contains script which hides the recaptcha. So when I add it with webView.loadUrl it runs immediately and hides recaptcha. I am already doing exactly same thing which you have described. All I want is that javascript code should not be executed IMMEDIATELY when I add it to webview
    – Zohab Ali
    Commented Aug 1, 2018 at 5:49
  • @ZohabAli, can you please update your question and post the full code you want to run including hiding the reCAPTCHA with all the additional details you want/need to do, so I can update my answer accordingly? Also, please post the HTML code too, so we can help you better. Thank you in advance.
    – Rick
    Commented Aug 3, 2018 at 10:52
  • 1
    Your solution helped me and my team to solve a not so simple problem. Can you please check the recaptcha link in your answer? Thx! Commented Feb 2, 2022 at 21:03
  • 1
    Fixed and updated. You're welcome!
    – Rick
    Commented Feb 3, 2022 at 18:32
0
+200

Try injecting the script like this,

function addCode(code){
var addedScript= document.createElement('script');
addedScript.text= code;
document.body.appendChild(addedScript);}

now call the function like,

val codeToExec = "function captchaResponse (token){" +
                    "android.reCaptchaCallbackInAndroid(token);" +
                    "}";

now exec loadurl like,

webview.loadUrl("javascript:(function addCode(code){
var addedScript= document.createElement('script');
addedScript.text= code;
document.body.appendChild(addedScript);})(codeToExec));

Not the answer you're looking for? Browse other questions tagged or ask your own question.