2025::Adding a custom Javascript API to Webkit
Say if we want to add a custom function so that we can terminate the whole browser from JS by calling window.NiNiTest.quit
, what should we do?
First, we have to create files NiNiTest.idl
and NiNiTest.h
under the WebCore project:
//// NiNiTest.idl// WebCore//// Created by NiNi on 2025/6/30.//
[ Exposed=Window] interface NiNiTest { [CallWith=CurrentDocument] static undefined quit();};
//// NiNiTest.h// WebCore//// Created by NiNi on 2025/6/30.//
#pragma once
#include "Document.h"#include "Chrome.h"#include "ChromeClient.h"#include <wtf/Ref.h>#include <wtf/RefCounted.h>
namespace WebCore {
class NiNiTest : public RefCounted<NiNiTest>{ public: static Ref<NiNiTest> create(ScriptExecutionContext& context) { return adoptRef(*new NiNiTest(downcast<Document>(context))); } static ExceptionOr<void> quit(Document& document) { if (auto* page = document.page()) page->chrome().client().requestBrowserQuit(); return { }; }
private: NiNiTest(Document&);
Ref<Document> m_document; };}
Appending a new line in Source/WebCore/Sources.txt
:
JSNiNiTest.cpp
Add $(WebCore)/NiNiTest.idl
to JS_BINDING_IDLS
in Source/WebCore/DerivedSources.txt
JS_BINDING_IDLS := \ $(WebCore)/NiNiTest.idl \
The webkit will generate corresponding JSNiNiTest.cpp
and JSNiNiTest.cpp
for us.
Now, because the requestBrowserQuit
in page->chrome().client().requestBrowserQuit();
doesn’t exist, we have to implement that function to make it come true. We add a virutal function in class ChromeClient
in /Source/WebCore/page/ChromeClient.h
class ChromeClient {public: virtual void requestBrowserQuit() { }
Then overwrite it in the derived class WebChromeClient
in Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.h
class WebChromeClient final : public WebCore::ChromeClient { WTF_MAKE_TZONE_ALLOCATED(WebChromeClient);public: WebChromeClient(WebPage&); ~WebChromeClient();
WebPage* page() const { return m_page.get(); } void requestBrowserQuit() final;
and Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.cpp
.
void WebChromeClient::requestBrowserQuit(){ m_page->send(Messages::WebPageProxy::RequestBrowserQuit());}
We try to send a message through IPC to WebPageProxy
, same, there is nothing can handle this message or even send it.
Adding a new function to class WebPageProxy
in Source/WebKit/UIProcess/WebPageProxy.h
class WebPageProxy final : public API::ObjectImpl<API::Object::Type::Page>, public IPC::MessageReceiver {public: static Ref<WebPageProxy> create(PageClient&, WebProcessProxy&, Ref<API::PageConfiguration>&&); virtual ~WebPageProxy();
void ref() const final { API::ObjectImpl<API::Object::Type::Page>::ref(); } void deref() const final { API::ObjectImpl<API::Object::Type::Page>::deref(); }
USING_CAN_MAKE_WEAKPTR(IPC::MessageReceiver); void requestBrowserQuit();
and Source/WebKit/UIProcess/WebPageProxy.cpp
void WebPageProxy::requestBrowserQuit(){ m_uiClient->runBrowserQuit(this);}
Adding it to the Source/WebKit/UIProcess/WebPageProxy.messages.in
messages -> WebPageProxy { RequestBrowserQuit() # UI messages
Actually close the application in by firstly adding a virtual function in in /Source/WebKit/UIProcess/API/APIUIClient.h
class UIClient { WTF_MAKE_TZONE_ALLOCATED(UIClient);public: virtual ~UIClient() { }
virtual void runBrowserQuit(WebKit::WebPageProxy*) { }
Overwriting it in UIClient
in Source/WebKit/UIProcess/Cocoa/UIDelegate.h
namespace WebKit {
enum class TapHandlingResult : uint8_t;
class UIDelegate : public CanMakeWeakPtr<UIDelegate> { WTF_MAKE_TZONE_ALLOCATED(UIDelegate);public: explicit UIDelegate(WKWebView *); ~UIDelegate();
void ref() const; void deref() const;
#if ENABLE(CONTEXT_MENUS) std::unique_ptr<API::ContextMenuClient> createContextMenuClient();#endif std::unique_ptr<API::UIClient> createUIClient();
RetainPtr<id <WKUIDelegate> > delegate(); void setDelegate(id <WKUIDelegate>);
private:#if ENABLE(CONTEXT_MENUS) class ContextMenuClient : public API::ContextMenuClient { WTF_MAKE_TZONE_ALLOCATED(ContextMenuClient); public: explicit ContextMenuClient(UIDelegate&); ~ContextMenuClient();
private: // API::ContextMenuClient void menuFromProposedMenu(WebPageProxy&, NSMenu *, const ContextMenuContextData&, API::Object*, CompletionHandler<void(RetainPtr<NSMenu>&&)>&&) override;
WeakPtr<UIDelegate> m_uiDelegate; };#endif
class UIClient : public API::UIClient, public CanMakeWeakPtr<UIClient> { WTF_MAKE_TZONE_ALLOCATED(UIClient); public: explicit UIClient(UIDelegate&); ~UIClient(); void runBrowserQuit(WebPageProxy*) final;
and Source/WebKit/UIProcess/Cocoa/UIDelegate.mm
void UIDelegate::UIClient::runBrowserQuit(WebPageProxy*){ [NSApp terminate:nil];}
If the webkit fail to find JSNiNiTest.h
, try to clean to build folder of WebCore then try again, that should solve the issue.

Refs
Original Author: terrynini38514
Original Link: https://blog.terrynini.tw/posts/2025-Adding-a-custom-Javascript-API-to-Webkit/
Publish at: July 9, 2025 at 08:00:00 (Taiwan Time)
Copyright: This article is licensed under CC BY-NC 4.0