How do I inject javascript to an IFrame from Java code into an Android WebView?

Issue

I’m writing an Android app that displays web pages in a WebView. some of them include IFrames. I don’t have control over which pages will be displayed.

Part of what the app does is inject javascript code into the pages from my Java code (adding a JavaScript interface and scripts). I can do it with the main page pretty easily but it looks like a more challenging task to do it in an IFrame.

Looks like the onPageFinished event of the WebViewClient class (the appropriate place to inject scripts) is only fired for the main page, not the IFrames within it.

Any ideas as to how to do this?

Solution

The only method to do this that I am aware of is to use a WebViewClient and override shouldInterceptRequest. Look for the url of the iframe you are wanting to inject something into, load that url manually into a stream or text, inject using string manipulation the content you want to add or whatever you want to remove, and then send that as the WebResourceResponse instead of the original request.

Here is some kotlin code which can be converted to java:

    webView.webViewClient  object : WebViewClient() {
        private fun shouldInjectToIframe(url: String?): Boolean {
            return !url.isNullOrBlank() && url.indexOf("<string to look for>") > -1
        }
        private fun injectToIframe(url: String): WebResourceResponse? {
            Log.d(TAG, "Intercepted $url")
            val latch  CountDownLatch(1)
            var res: InputStream?  null
            val call  App.instance?.okHttpClient?.newCall(Request.Builder().url(url).build())
            call?.enqueue(object : Callback {
                override fun onFailure(call: Call, e: IOException) {
                    latch.countDown()
                }

                override fun onResponse(call: Call, response: Response) {
                    res  response.body?.byteStream()
                    latch.countDown()
                }
            })

            latch.await()

            val reader  BufferedReader(res?.reader())
            var content: String
            try {
                content  reader.readText()
            } finally {
                reader.close()
            }

            var scriptToInject  "\n&lt;script>\n" +
                "   (function() {\n" +
                "     alert('hi');\n" +
                "   })()\n" +
                "&lt;/script>\n"
            val newContent  "${content.split("&lt;/head>")[0]}${scriptToInject}</head>${content.split("&lt;/head>")[1]}"
            Log.d(TAG, "Inject script to iframe: $newContent")
            val inStream  newContent.byteInputStream()
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return WebResourceResponse(
                "text/html",
                "utf-8",
                inStream
            )
            val statusCode  200
            val reasonPhase  "OK"
            val responseHeaders: MutableMap<String, String>  HashMap()
            return WebResourceResponse("text/html", "utf-8", statusCode, reasonPhase, responseHeaders, inStream)
        }

        override fun shouldInterceptRequest(view: WebView?, url: String?): WebResourceResponse? {
            if (shouldInjectToIframe(url)) return injectToIframe(url!!)
            if (Util.SDK_INT &lt; Build.VERSION_CODES.LOLLIPOP) return super.shouldInterceptRequest(view, url)
            return null
        }

        @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
        override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest): WebResourceResponse? {
            if (shouldInjectToIframe(request.url.toString())) return injectToIframe(request.url.toString())
            return super.shouldInterceptRequest(view, request)
        }
    }

Answered By – Chad Michael

Leave a Comment