Customising
the web:
browsers as user agents

delan azabani (she/her)
azabani.com
november 2023

Browsers as user agents

Empower users to interact with the web on their own terms!

my general process is that every time i am feeling complainthoughts about a website, i think, wait, i could do something about that.

Why customise the web?

  • [cohost, mastodon, twitter]
  • Accessibility (26)
  • Readability and layout (31)
  • Automation and user rights (26)
  •  Blocking non-ad content (18)
  •  Bugs and features (18)
  •  Personalisation (11)
  •  Other (2)

More survey responses

  • Blocking ads (∞)
  • Accessibility (26)
  • Readability and layout (31)
  • Automation and user rights (26)
  • Blocking other content (18)
    [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]
  • Redirecting to preferred websites (6)
    [1, 2, 3, 4, 5, 6]
  • Improving media controls (6) [1, 2, 3, 4, 5, 6]
  • Organising information (5) [1, 2, 3, 4, 5]
  • Fixing website bugs (4) [1, 2, 3, 4]
  • Personalising websites (4) [1, 2, 3, 4]
  • Improving notifications/replies (3) [1, 2, 3]
  • Game modding(!)
  • Other changes (2) [1, 2]
looking through my user styles it's a lot of making things more accessible.

Accessibility

cohost is really difficult to use with a screen reader, so i made a userscript that adds aria labels and fixes incorrect roles. for me when my eyes are dying

Readability and layout

Automation & user rights

  • Data entry (6) [1, 2, 3, 4, 5, 6]
  • Bulk downloading/archiving (5) [1, 2, 3, 4, 5]
  • Bypassing {pay,signup}walls (4) [1, 2, 3, 4]
  • Canned responses (2) [1, 2]
  • Exercising cookie consent (2) [1, 2]
  • Go to next/parent URL (2) [1, 2]
  • Disabling security theatre (2) [1, 2]
  • Other such changes (3) [1, 2, 3]
i wrote scripts to automatically deny all on various GDPR “we respect your privacy” consent popups, [and a] script that automatically enables captions on youtube videos (if available) because i like having them
I made a userscript that injected a button […] into the CMS that opened an auto-populated AIM message [to my micromanaging boss].

Tools for customisation

  • Author tools
    • styles
    • scripts
    • interactivity
  • User tools
    • user styling
    • user scripting
    • bookmarklets

User styling

I don't really do my own coding or writing my own scripts or anything, but I DO […] write my own custom CSS […] for pretty much every website I use frequently.
i think i have a userstyle for almost every site i use regularly

User scripting

Bookmarklets

Content blocking

We use cookies and other tracking technologies to improve your browsing experience on our website, to show you personalised content and targeted ads, to analyse our website traffic, and to understand where our visitors are coming from.
Hi, how can I help you today?
👩‍💼
Igalia NFT now selling!
Limited edition! Don’t wait!

Readability

Work account warning

Userstyle gotchas

(author origin)

User stylesheet gotchas

(user origin)

Writing user scripts

No more GitHub scrapers

(see also full version, which rewrites Google search results too)

        // ==UserScript==
        // @match        https://githubmemory.com/repo/*
        // @match        https://gitmemory.com/issue/*
        // @match        https://issueexplorer.com/issue/*
        // @match        https://uonfu.com/q/*
        // ==/UserScript==

        const BAD = {
            "githubmemory.com": {pathPrefix: "/repo/", userIndex: 2, repoIndex: 3, issueIndex: 5},
            "gitmemory.com": {pathPrefix: "/issue/", userIndex: 2, repoIndex: 3, issueIndex: 4},
            "issueexplorer.com": {pathPrefix: "/issue/", userIndex: 2, repoIndex: 3, issueIndex: 4},
            "uonfu.com": {pathPrefix: "/q/", userIndex: 2, repoIndex: 3, issueIndex: 4},
        };

        if (BAD.hasOwnProperty(location.hostname)) {
            const {pathPrefix, userIndex, repoIndex, issueIndex} = BAD[location.hostname];
            if (location.pathname.startsWith(pathPrefix)) {
                const components = location.pathname.split("/");
                const [user, repo, issue] = [userIndex, repoIndex, issueIndex].map(x => components[x]);
                location.replace(`https://github.com/${user}/${repo}/issues/${issue}`);
            }
        }
    

No more GitHub scrapers

(see also full version, which rewrites Google search results too)

        // ==UserScript==
        // @match        https://githubmemory.com/repo/*
        // @match        https://gitmemory.com/issue/*
        // @match        https://issueexplorer.com/issue/*
        // @match        https://uonfu.com/q/*
        // ==/UserScript==

        const BAD = {
            "githubmemory.com": {pathPrefix: "/repo/", userIndex: 2, repoIndex: 3, issueIndex: 5},
            "gitmemory.com": {pathPrefix: "/issue/", userIndex: 2, repoIndex: 3, issueIndex: 4},
            "issueexplorer.com": {pathPrefix: "/issue/", userIndex: 2, repoIndex: 3, issueIndex: 4},
            "uonfu.com": {pathPrefix: "/q/", userIndex: 2, repoIndex: 3, issueIndex: 4},
        };

        if (BAD.hasOwnProperty(location.hostname)) {
            const {pathPrefix, userIndex, repoIndex, issueIndex} = BAD[location.hostname];
            if (location.pathname.startsWith(pathPrefix)) {
                const components = location.pathname.split("/");
                const [user, repo, issue] = [userIndex, repoIndex, issueIndex].map(x => components[x]);
                location.replace(`https://github.com/${user}/${repo}/issues/${issue}`);
            }
        }
    

No more GitHub scrapers

(see also full version, which rewrites Google search results too)

        // ==UserScript==
        // @match        https://githubmemory.com/repo/*
        // @match        https://gitmemory.com/issue/*
        // @match        https://issueexplorer.com/issue/*
        // @match        https://uonfu.com/q/*
        // ==/UserScript==

        const BAD = {
            "githubmemory.com": {pathPrefix: "/repo/", userIndex: 2, repoIndex: 3, issueIndex: 5},
            "gitmemory.com": {pathPrefix: "/issue/", userIndex: 2, repoIndex: 3, issueIndex: 4},
            "issueexplorer.com": {pathPrefix: "/issue/", userIndex: 2, repoIndex: 3, issueIndex: 4},
            "uonfu.com": {pathPrefix: "/q/", userIndex: 2, repoIndex: 3, issueIndex: 4},
        };

        if (BAD.hasOwnProperty(location.hostname)) {
            const {pathPrefix, userIndex, repoIndex, issueIndex} = BAD[location.hostname];
            if (location.pathname.startsWith(pathPrefix)) {
                const components = location.pathname.split("/");
                const [user, repo, issue] = [userIndex, repoIndex, issueIndex].map(x => components[x]);
                location.replace(`https://github.com/${user}/${repo}/issues/${issue}`);
            }
        }
    

Universal next button


        ((pathname) => {
            console.log(pathname);
        })(location.pathname.split("/"))
    

Universal next button


        ((pathname) => {
            for (const [i,name] of pathname.entries()) {
                if (name == parseInt(name,10)) {
                    pathname[i]++;
                    location.pathname = pathname.join("/");
                    return;
                }
            }
        })(location.pathname.split("/"))
    

Universal next button

javascript:((pathname) => { for (const [i,name] of pathname.entries()) { if (name == parseInt(name,10)) { pathname[i]++; location.pathname = pathname.join("/"); return; } } })(location.pathname.split("/"))

Copying text

Who would win?
203 characters of clipboard API
(c=>{document.addEventListener("copy",c);document.execCommand("copy");document.removeEventListener("copy",c)})(e=>{e.clipboardData.setData("text/plain","your text");e.preventDefault();e.stopImmediatePropagation()})
- or -

How we can help

Thanks!