Android WebView – Intercepting and Blocking Requests

  • infoFull Post Details
    info_outlineClick for Full Post Details
    Date Posted:
    Jun. 02, 2021
    Last Updated:
    Jun. 02, 2021
  • classTags
    classClick for Tags

Intro

One of the neat things about WebView on Android is the level of control that you are given over the WebView component. In addition to controlling settings and navigating to URLs, you can actually control the WebView at the network request level, intercepting network calls, modifying content, or even blocking requests altogether.

This post is about how to implement some network request blocking (and modification) with WebView and related classes, such as WebViewClient. Examples will focus on Kotlin, but would apply to Java as well with just a little tweaking.

Where to Hook into WebView Requests

There are a lot of different ways to customize your WebView within Android code; settings, callbacks, WebChromeClient, and WebViewClient.

For those wondering what the difference is between WebChromeClient and WebViewClient, the answer is mostly in separation of concerns. The WebChromeClient class is mostly concerned with long-lived browser-level things, such as the user’s visit history, whereas WebViewClient has more to do with the lifecycle of each individual webpage and things like hooking into when a page has finished loading.

For hooking into network requests, the easiest entry point is by subclassing WebViewClient and overriding two important functions:

These methods both do similar things; they let you, as a developer, intercept and modify how each browser request is handled.

The difference is a little subtle between these methods; although AJAX requests use URLs, the shouldOverrideUrlLoading is really only called for the URL of the webpage itself – e.g. the one that the user types in. AJAX and other requests will not come through this hook, but will flow through shouldInterceptRequest.

Writing our WebView Request Modifiers

Since we will want to use both hooks (shouldOverrideUrlLoading and shouldInterceptRequest), and both have identical method signatures, this is an ideal situation to keep our code clean and have a single function that can be called by both:

fun filterNetworkRequests(view: WebView, request: WebResourceRequest): Boolean {
    // By default, let all requests pass through
    var shouldBlock = false;

    // ....
    // code to determine if shouldBlock should be changed

    return shouldBlock
}

Although we can call this function from both hooks with identical arguments, how we use the response actually changes in each.

  • shouldOverrideUrlLoading: Returning true will abort / block the URL being loaded, false allows it to proceed as normal
  • shouldInterceptRequest: To block the request, we need to actually return our own custom response which should replace the actual one. Returning null will allow it to proceed as normal.

Knowing this, here is how we could use our common function within each hook:

override fun shouldInterceptRequest(
    view: WebView,
    request: WebResourceRequest
): WebResourceResponse? {
    val shouldBlock = filterNetworkRequests(webView, request);

    if (shouldBlock) {
        return WebResourceResponse("text/javascript", "UTF-8", null)
    }

    return null
}

override fun shouldOverrideUrlLoading(
    view: WebView,
    request: WebResourceRequest
): Boolean {
    return filterNetworkRequests(view, request)
}

Implementing Blocking Patterns

To take this a step further, I’m going to demonstrate how you could use these hooks with blocklists to prevent any requests from being fulfilled for certain domains or URL patterns.

Road with a sign next to it reading "Road Ends"

First, let’s declare our block lists:

val BannedDomains: Array<String> = arrayOf(
    "facebook.com",
    "connect.facebook.net"
)
val BannedUrlPatterns: Array<Regex> = arrayOf(
    Regex("\\.taboola\\.")
)

Next, we can modify our reusable filterNetworkRequests function to take these into account:

fun filterNetworkRequests(view: WebView, request: WebResourceRequest): Boolean {
    // By default, let all requests pass through
    var shouldBlock = false;
    val reqUri = request.url
    val reqUrl = reqUri.toString()

    if (BannedDomains.contains(reqUri.host?.replace("www.", ""))) {
        shouldBlock = true
    }

    for (bannedPattern in BannedUrlPatterns) {
        if (!shouldBlock) {
            shouldBlock = bannedPattern.containsMatchIn(reqUrl)
        }
    }

    return shouldBlock
}

Request Blocking in WebView: A Complete Demo

Since sometimes it helps to see everything put together, here is a complete (although basic) working demo of request blocking with WebView, using the previous steps we built out.

Loading Github Gist: https://gist.github.com/joshuatz/e801edbe9f545ebddb2352f2d1941db3

Leave a Reply

Your email address will not be published. Required fields are marked *