<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Matheus Castro</title>
    <description>The latest articles on Forem by Matheus Castro (@matheusccastro).</description>
    <link>https://forem.com/matheusccastro</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2986166%2F17be3d94-dbcd-4180-96a1-a3769071828e.png</url>
      <title>Forem: Matheus Castro</title>
      <link>https://forem.com/matheusccastro</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/matheusccastro"/>
    <language>en</language>
    <item>
      <title>Intercepting requests from WebView</title>
      <dc:creator>Matheus Castro</dc:creator>
      <pubDate>Fri, 28 Mar 2025 15:26:25 +0000</pubDate>
      <link>https://forem.com/quave/intercepting-requests-from-webview-1nin</link>
      <guid>https://forem.com/quave/intercepting-requests-from-webview-1nin</guid>
      <description>&lt;p&gt;Have you ever wondered how you can intercept &lt;strong&gt;ANY&lt;/strong&gt; requests that your WebView makes in both &lt;code&gt;Android&lt;/code&gt; and &lt;code&gt;iOS&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;Well, turns out that this is possible and not so hard to do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Android
&lt;/h2&gt;

&lt;p&gt;For &lt;code&gt;Android&lt;/code&gt; it's actually pretty simple! You need to create a custom &lt;code&gt;web view client&lt;/code&gt; and override the &lt;code&gt;shouldInterceptRequest&lt;/code&gt; function. So it would look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nc"&gt;WebView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;webViewClient&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="err"&gt;: &lt;/span&gt;&lt;span class="nc"&gt;WebViewClientCompat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;shouldInterceptRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;WebView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;WebResourceRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;WebResourceResponse&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;requestHost&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;
                &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;isRequestAllowed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
                    &lt;span class="n"&gt;allowedHostsList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;any&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;requestHost&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isRequestAllowed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="c1"&gt;// Here you can return null or call super.shouldInterceptRequest(view, request)&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;

                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;WebResourceResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"text/html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"utf-8"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Blocked"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;emptyMap&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;ByteArrayInputStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ByteArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This method allows you to intercept the request and handle it as you may see fit. Remember that the &lt;code&gt;request.url.host&lt;/code&gt; is a string that contains &lt;strong&gt;only&lt;/strong&gt; the host, so no &lt;code&gt;pathname&lt;/code&gt; or &lt;code&gt;protocol&lt;/code&gt; there. If you want to get this information, you can get it from the &lt;code&gt;request.url&lt;/code&gt; object.&lt;/p&gt;

&lt;h3&gt;
  
  
  iOS
&lt;/h3&gt;

&lt;p&gt;For &lt;code&gt;iOS&lt;/code&gt; it's not so straightforward. Actually there's no way to intercept the request, so we need to resort to a little trick. You can intercept navigation requests (going from one &lt;code&gt;url&lt;/code&gt; to another), with &lt;code&gt;WKNavigationDelegate&lt;/code&gt;, but not requests made by the &lt;code&gt;javascript&lt;/code&gt; from the website, for example.&lt;/p&gt;

&lt;p&gt;To our rescue, we can use &lt;code&gt;WKContentRuleList&lt;/code&gt;, which in essence is a list that works in the same way as the &lt;a href="https://developer.apple.com/documentation/SafariServices/creating-a-content-blocker" rel="noopener noreferrer"&gt;content blockers&lt;/a&gt; from &lt;code&gt;Safari&lt;/code&gt; (the browser).&lt;/p&gt;

&lt;p&gt;We can use this content rule list to block requests to specific &lt;code&gt;urls&lt;/code&gt;. First, we need to define the rule list, compile it and add to our &lt;code&gt;webview&lt;/code&gt;. There's a gotcha, tough, we need to first block everything and then allow the domains we want (it's a trick, after all).&lt;/p&gt;

&lt;p&gt;The rule list is a simple &lt;code&gt;json array&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"trigger"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"url-filter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".*"&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"block"&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"trigger"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"url-filter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"google.com"&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ignore-previous-rules"&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The composition of the rule is pretty simple, you have a &lt;code&gt;trigger&lt;/code&gt; object, and a &lt;code&gt;action&lt;/code&gt; one.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;trigger&lt;/code&gt; key component is the &lt;code&gt;url-filter&lt;/code&gt;, which accepts a &lt;code&gt;regex&lt;/code&gt; to match the &lt;code&gt;url&lt;/code&gt;, and &lt;code&gt;.*&lt;/code&gt; will match &lt;strong&gt;all&lt;/strong&gt; &lt;code&gt;urls&lt;/code&gt;.&lt;br&gt;
The &lt;code&gt;action&lt;/code&gt; key component is the &lt;code&gt;type&lt;/code&gt;, which will define what will happen for the matched &lt;code&gt;url&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can check the complete list of options for the &lt;code&gt;trigger&lt;/code&gt; &lt;a href="https://developer.apple.com/documentation/SafariServices/creating-a-content-blocker#Add-triggers-to-your-content-blocker" rel="noopener noreferrer"&gt;here&lt;/a&gt; and for the &lt;code&gt;action&lt;/code&gt; block &lt;a href="https://developer.apple.com/documentation/SafariServices/creating-a-content-blocker#Add-actions-to-your-content-blocker" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this example, we first block all requests, and then we allow only requests to &lt;code&gt;google.com&lt;/code&gt; by defining the action of type &lt;code&gt;ignore-previous-rules&lt;/code&gt;, which as the name implies, will ignore any rule that the url matched before.&lt;/p&gt;

&lt;p&gt;After having your list set up, you need to compile and apply it to the &lt;code&gt;userContentController&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;contentRulesList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"""
[
    {
                "&lt;/span&gt;&lt;span class="n"&gt;trigger&lt;/span&gt;&lt;span class="s"&gt;": {
                    "&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="s"&gt;": "&lt;/span&gt;&lt;span class="o"&gt;.*&lt;/span&gt;&lt;span class="s"&gt;"
                },
                "&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="s"&gt;": {
                    "&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="s"&gt;": "&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="s"&gt;"
                }
    },
    {
                "&lt;/span&gt;&lt;span class="n"&gt;trigger&lt;/span&gt;&lt;span class="s"&gt;": {
                    "&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="s"&gt;": "&lt;/span&gt;&lt;span class="n"&gt;google&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="s"&gt;"
                },
                "&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="s"&gt;": {
                    "&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="s"&gt;": "&lt;/span&gt;&lt;span class="n"&gt;ignore&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;previous&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="s"&gt;"
                }
    }
]
"""&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;compiledContentRulesList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="kt"&gt;WKContentRuleListStore&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compileContentRuleList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="nv"&gt;forIdentifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"MyAppList"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="nv"&gt;encodedContentRuleList&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;contentRulesList&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;webViewConfiguration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;userContentController&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;compiledContentRulesList&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, you are all set!&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Now you know how to intercept and block/allow requests for &lt;code&gt;webviews&lt;/code&gt; in both &lt;code&gt;android&lt;/code&gt; and &lt;code&gt;iOS&lt;/code&gt;.&lt;br&gt;
For &lt;code&gt;android&lt;/code&gt; it's pretty simple and straighforward, but for &lt;code&gt;iOS&lt;/code&gt; we need the &lt;code&gt;WKContentRuleList&lt;/code&gt; trick. Hopefully we will have a more direct way in the future.&lt;/p&gt;

&lt;p&gt;Thanks for reading! If you have any questions, let me know.&lt;/p&gt;

</description>
      <category>webview</category>
      <category>ios</category>
      <category>android</category>
      <category>mobile</category>
    </item>
  </channel>
</rss>
