Adding a Blocked-URL Callback to WKNavigationDelegate
How I added a WebKit callback to report blocked URLs in my browser, from WebCore through IPC to Swift.
My browser uses over 100,000 network-blocking rules. I wanted to see which URLs they blocked, so I added a callback to WKNavigationDelegate in my WebKit fork.
Detecting Blocked URLs
CachedResourceLoader.cpp contains a function called requestResource which handles resource loading and performs content-blocking checks as part of that process
I wanted a delegate function in swift which I can read these URLs
The Function performed:
const auto& resourceRequest = request.resourceRequest();
resourceRequest contains a url() function
so I converted it to a WTF::String and forwarded it through WebCore’s Chrome interface:
String urlStr = resourceRequest.url().string();
page->chrome().client().didJustBlock(urlStr);
this calls a virtual method named didJustBlock defined in ChromeClient.h:
virtual void didJustBlock(String&) { };
The client we needed to implement this on was WebChromeClient.cpp. WebChromeClient is a child class of ChromeClient so we can just override its function in WebChromeClient.h:
void didJustBlock(String&) override;
WebChromeClient.cpp does:
void WebChromeClient::didJustBlock(String& url)
{
if (RefPtr page = m_page.get()) {
page->send(Messages::WebPageProxy::DidJustBlock(url));
}
}
IPC - WebProcess to UIProcess
when the WebChromeClient.cpp did:
page->send(Messages::WebPageProxy::DidJustBlock(url));
Messages::WebPageProxy::DidJustBlock(url) is a message created by a .in file WebPageProxy.messages.in
We can just write
DidJustBlock(String url)
And while compiling, it generates the message in WebPageProxyMessages.h under Messages::WebPageProxy
This is now when the IPC message starts, WebPageProxy.h must define the function:
void didJustBlock(String&&);
The IPC Part is interesting
an IPC Message is sent from WebChromeClient.cpp using page->send(...), WebKit’s generated IPC Machinery serializes and delivers the message to WebPageProxy in the UI process, where didJustBlock(String&&) handles it
Runtime Polymorphism
before we implement the didJustBlock(String&&) inside of the WebPageProxy we create the function that it can call
inside of APINavigationClient.h: we implement:
virtual void didJustBlock(WebKit::WebPageProxy&, Ref<API::String>&) { };
This means any child classes can implement this, and its useful to us in the WebPageProxy.cpp where can now implement the function didJustBlock:
void WebPageProxy::didJustBlock(String&& url)
{
Ref<API::String> toSendUrl = API::String::create(WTF::move(url));
m_navigationClient->didJustBlock(*this, toSendUrl);
}
m_navigationClient is a unique reference to the base class API::NavigationClient.
m_navigationClient is defined like this in the header:
UniqueRef<API::NavigationClient> m_navigationClient;
the API:: only exists because NavigationClient is under namespace API{}
WKWebView.mm sets the m_navigationClient when we assign a WKNavigationDelegate:
- (void)setNavigationDelegate:(id <WKNavigationDelegate>)navigationDelegate
{
_page->setNavigationClient(_navigationState->createNavigationClient());
_navigationState->setNavigationDelegate(navigationDelegate);
}
Which calls:
void WebPageProxy::setNavigationClient(UniqueRef<API::NavigationClient>&& navigationClient)
{
m_navigationClient = WTF::move(navigationClient);
}
_navigationState is just: NavigationState its create function looks like this:
UniqueRef<API::NavigationClient> NavigationState::createNavigationClient()
{
return makeUniqueRef<NavigationClient>(*this);
}
This is allowed because NavigationState in its private header does this:
class NavigationClient final : public API::NavigationClient {
so now we know that that WebPageProxy’s m_navigationClient now holds a NavigationState::NavigationClient
We had just added the function:
virtual void didJustBlock(WebKit::WebPageProxy&, Ref<API::String>&) { };
inside of API::NavigationClient
because NavigationState::NavigationClient inherits from API::NavigationClient it can override the virtual function defined in the base class:
void didJustBlock(WebPageProxy&, Ref<API::String>&) override;
So before we implement this inside of NavigationState.mm we need to make sure that the delegate function can call this.
Implementing The Delegate Function
so in the WKNavigationDelegate.h we can write this:
- (void)webView:(WKWebView *)webView didJustBlock:(NSURL *)blockedURL;
This allows a user to implement this function when they assign the WKNavigationDelegate
When we do:
webView.navigationDelegate = self
in swift This implicitly calls:
NavigationState::setNavigationDelegate(id<WKNavigationDelegate> delegate)
This function assigns delegate functions
It works like this:
Swift
webView.navigationDelegate = self
|
v
WKWebView.mm
-setNavigationDelegate:
|
+--> WebPageProxy::setNavigationClient(...)
|
+--> NavigationState::setNavigationDelegate(...)
So for our implementation to work, we go to the m_navigationDelegateMethods struct inside of NavigationState and add a flag:
bool webViewDidJustBlock: 1;
Now we can do this in setNavigationDelegate:
m_navigationDelegateMethods.webViewDidJustBlock = [delegate respondsToSelector:@selector(webView:didJustBlock:)];
From earlier we just override’d the function call:
void didJustBlock(WebPageProxy&, Ref<API::String>&) override;
We can now implement it in NavigationState.mm
void NavigationState::NavigationClient::didJustBlock(WebKit::WebPageProxy&, Ref<API::String>& url)
{
RefPtr navigationState = m_navigationState.get();
if (!navigationState)
return;
auto navigationDelegate = navigationState->navigationDelegate();
if (!navigationDelegate)
return;
if (navigationState->m_navigationDelegateMethods.webViewDidJustBlock) {
NSURL *nsURL = [NSURL URLWithString:url->string().createNSString().autorelease()];
if (!nsURL)
return;
[navigationDelegate webView:navigationState->webView().get() didJustBlock:(nsURL)];
}
}
Using it in Swift
This callback requires building and running against my ComfyWebKit fork.
My browser’s navigation delegate receives blocked URLs like this:
func webView(_ webView: WKWebView, didJustBlock blockedURL: URL) {
print("WEB VIEW DID JUST BLOCK: \(blockedURL.absoluteString)")
}