FunkSoulBrother
FunkSoulBrother

Reputation: 2167

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

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?

Upvotes: 3

Views: 2203

Answers (1)

Chad Michael
Chad Michael

Reputation: 266

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

Upvotes: 3

Related Questions