Hi y'all, The attached patch adds support for user styles and scripts, two WebKit APIs for URL-based style/script injection. Mostly just parsing and injection of those here, but also a new structure (UserContent) and a bug:
When applying styles (old SiteSpecific ones, from styledir), current implementation does webkit_user_content_manager_remove_all_style_sheets and thus disables the ones I've added. How do we approach this? I'm thinking either - Storing the applied script inside the SiteSpecific object and only removing it, instead of all styles. Lightweight, but changes the struct layout. - Re-injecting all the styles on every page load, along with the old site-specific styles. That's a lot of allocations. - Moving to the user styles infra exclusively. Not a good decision. Deprecating stuff is not fun. And the user content allowlist patterns are not as flexible as regex either.
>From e0f9c36dca1a1ca90d9b1777d374af142b89ef92 Mon Sep 17 00:00:00 2001 From: Artyom Bologov <g...@aartaka.me> Date: Sun, 20 Apr 2025 23:11:10 +0400 Subject: [PATCH] Support user scripts and user styles. --- config.def.h | 11 ++++++++++ surf.c | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/config.def.h b/config.def.h index 9b0f6f6..8ee9808 100644 --- a/config.def.h +++ b/config.def.h @@ -3,6 +3,7 @@ static int surfuseragent = 1; /* Append Surf version to default WebKit user static char *fulluseragent = ""; /* Or override the whole user agent string */ static char *scriptfile = "~/.surf/script.js"; static char *styledir = "~/.surf/styles/"; +static char *usercontentdir = "~/.surf/usercontent/"; static char *certdir = "~/.surf/certificates/"; static char *cachedir = "~/.surf/cache/"; static char *cookiefile = "~/.surf/cookies.txt"; @@ -121,6 +122,16 @@ static SiteSpecific certs[] = { { "://suckless\\.org/", "suckless.org.crt" }, }; +static UserContent userstyles[] = { + /* patterns file in $usercontentdir priority*/ + { "*://*/*", "default.css", 1} +}; + +static UserContent userscripts[] = { + /* patterns file in $usercontentdir priority*/ + { "*://*/*", "default.js", 1} +}; + #define MODKEY GDK_CONTROL_MASK /* hotkeys */ diff --git a/surf.c b/surf.c index 7a0f624..188a3eb 100644 --- a/surf.c +++ b/surf.c @@ -144,6 +144,13 @@ typedef struct { regex_t re; } SiteSpecific; +typedef struct { + const char *allow_pattern; + char *file; + int prio; + char *slurped; +} UserContent; + /* Surf */ static void die(const char *errstr, ...); static void usage(void); @@ -329,7 +336,9 @@ setup(void) { GIOChannel *gchanin; GdkDisplay *gdpy; + char *contentpath, *slurped; int i, j; + size_t l; /* clean up any zombies immediately */ sigchld(0); @@ -354,6 +363,7 @@ setup(void) /* dirs and files */ cookiefile = buildfile(cookiefile); scriptfile = buildfile(scriptfile); + usercontentdir = buildpath(usercontentdir); certdir = buildpath(certdir); if (curconfig[Ephemeral].val.i) cachedir = NULL; @@ -403,6 +413,20 @@ setup(void) stylefile = buildfile(stylefile); } + usercontentdir = buildpath(usercontentdir); + + for (i = 0; i < LENGTH(userstyles); ++i) { + contentpath = g_strconcat(usercontentdir, "/", userstyles[i].file, NULL); + g_file_get_contents(contentpath, &slurped, &l, NULL); + userstyles[i].slurped = slurped; + } + + for (i = 0; i < LENGTH(userscripts); ++i) { + contentpath = g_strconcat(usercontentdir, "/", userscripts[i].file, NULL); + g_file_get_contents(contentpath, &slurped, &l, NULL); + userscripts[i].slurped = slurped; + } + for (i = 0; i < LENGTH(uriparams); ++i) { if (regcomp(&(uriparams[i].re), uriparams[i].uri, REG_EXTENDED)) { @@ -1093,6 +1117,7 @@ cleanup(void) g_free(cookiefile); g_free(scriptfile); g_free(stylefile); + g_free(usercontentdir); g_free(cachedir); XCloseDisplay(dpy); } @@ -1100,11 +1125,14 @@ cleanup(void) WebKitWebView * newview(Client *c, WebKitWebView *rv) { + int i; WebKitWebView *v; WebKitSettings *settings; WebKitWebContext *context; WebKitCookieManager *cookiemanager; WebKitUserContentManager *contentmanager; + WebKitUserStyleSheet *style; + WebKitUserScript *script; /* Webview */ if (rv) { @@ -1142,6 +1170,39 @@ newview(Client *c, WebKitWebView *rv) contentmanager = webkit_user_content_manager_new(); + for (i = 0; i < LENGTH(userstyles); ++i) { + if (userstyles[i].slurped) { + const char *allowlist[2] = {userstyles[i].allow_pattern, NULL}; + style = webkit_user_style_sheet_new( + userstyles[i].slurped, + WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES, + (userstyles[i].prio + ? WEBKIT_USER_STYLE_LEVEL_USER + : WEBKIT_USER_STYLE_LEVEL_AUTHOR), + allowlist, + NULL); + webkit_user_content_manager_add_style_sheet(contentmanager, style); + webkit_user_style_sheet_unref(style); + } + } + + for (i = 0; i < LENGTH(userscripts); ++i) { + if (userscripts[i].slurped) { + printf("Script for %s is \n%s\n", userscripts[i].allow_pattern, userscripts[i].slurped); + const char *allowlist[2] = {userscripts[i].allow_pattern, NULL}; + script = webkit_user_script_new( + userscripts[i].slurped, + WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES, + (userscripts[i].prio + ? WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_END + : WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START), + allowlist, + NULL); + webkit_user_content_manager_add_script(contentmanager, script); + webkit_user_script_unref(script); + } + } + if (curconfig[Ephemeral].val.i) { context = webkit_web_context_new_ephemeral(); } else { -- 2.48.1
Regardless of what we decide, it can belong to a different patch. The user script/style injection is ready and works properly... once the issue with style erasure is resolved. Thanks, -- Artyom Bologov https://aartaka.me