<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Todd Schiller - Browser Extensions</title><link href="https://toddschiller.com/" rel="alternate"></link><link href="https://toddschiller.com/feeds/browser-extensions.atom.xml" rel="self"></link><id>https://toddschiller.com/</id><updated>2026-05-26T00:00:00-04:00</updated><subtitle>Human ✘ Artificial Intelligence</subtitle><entry><title>Did Google sneak a local LLM model into Chrome?</title><link href="https://toddschiller.com/blog/chrome-local-ai-linkedin-filter.html" rel="alternate"></link><published>2026-05-26T00:00:00-04:00</published><updated>2026-05-26T00:00:00-04:00</updated><author><name>Todd Schiller</name></author><id>tag:toddschiller.com,2026-05-26:/blog/chrome-local-ai-linkedin-filter.html</id><summary type="html">A response to FUD around Chrome's new Local AI models, plus a demo using PixieBrix + Local AI to filter my LinkedIn feed.</summary><content type="html">&lt;p&gt;There's a lot of FUD around Chrome's new Local AI models. Jason Calacanis on the
All-in Podcast got it wrong: Chrome didn't sneak in a local LLM model; it was in
their official Early Preview Program for months.&lt;/p&gt;
&lt;p&gt;The local LLM shipped in 148 is their general Prompt API powered by Gemini Nano.
Smaller, task-specific models for language detection, translation, and rewriting
have been available since Chrome 138 (June 2025), a long time at AI pace!&lt;/p&gt;
&lt;p&gt;Local LLMs distributed with the browser are critical to a future where users
control their browsing experience while ensuring privacy. Consumers cannot be
expected to figure out how to connect their web tools and extensions to Ollama
or LM Studio. And enterprises cannot be expected to deploy local LLM servers to
desktops.&lt;/p&gt;
&lt;p&gt;There are still valid concerns about model lock-in. That's because AI models (
especially small models) can behave differently for the same prompt. But, from
what I've seen, the Chrome team has been, by and large, responsible in how
they've rolled out the technology. For example, the public API shipped in 148
does not expose model-specific parameters.&lt;/p&gt;
&lt;p&gt;Local LLMs enable a range of productivity/compliance use cases, especially for
regulated industries handling financial and health data. But since this is
LinkedIn, here's a fun one instead: using PixieBrix + Local AI to customize your
LinkedIn feed and hide self-promotional, snarky, or sarcastic posts. The
question is -- will anything be left on my feed? 😆&lt;/p&gt;
&lt;!-- markdownlint-disable MD013 --&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0;"&gt;
  &lt;iframe src="https://www.loom.com/embed/8a1082b28512434588447b05b32594e1?hideEmbedTopBar=true" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;!-- markdownlint-enable MD013 --&gt;
</content><category term="Browser Extensions"></category><category term="AI"></category><category term="Chrome"></category><category term="browser extensions"></category><category term="PixieBrix"></category><category term="local AI"></category></entry><entry><title>Making AI coding work for enterprise-grade browser extensions</title><link href="https://toddschiller.com/blog/ai-coding-browser-extensions.html" rel="alternate"></link><published>2026-02-27T00:00:00-05:00</published><updated>2026-02-27T00:00:00-05:00</updated><author><name>Todd Schiller</name></author><id>tag:toddschiller.com,2026-02-27:/blog/ai-coding-browser-extensions.html</id><summary type="html">Lessons learned using AI coding tools to build PixieBrix, an enterprise-grade browser extension with a complex architecture spanning content scripts, background workers, and React UI.</summary><content type="html">&lt;p&gt;&lt;em&gt;This is a transcript of my talk at the
&lt;a href="https://aicodingsummit.com/"&gt;AI Coding Summit&lt;/a&gt; on February 26, 2026.
&lt;a href="https://gitnation.com/contents/making-ai-coding-work-for-enterprise-grade-browser-extensions"&gt;Watch the video on GitNation&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://toddschiller.com/assets/images/ai-coding-summit/title.png" alt="Title slide: Making AI Coding Work for Enterprise-Grade Browser Extensions" loading="lazy" decoding="async" /&gt;&lt;/p&gt;
&lt;p&gt;Hi, I'm Todd Schiller, co-founder of PixieBrix. Today I'm going to be talking
through some of the hard-won lessons that we've learned using AI to code an
AI-enabled extension that's used globally by enterprises.&lt;/p&gt;
&lt;h2&gt;Browser Extensions Enable Permissionless Innovation&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://toddschiller.com/assets/images/ai-coding-summit/permissionless-innovation.png" alt="Extensions enable permissionless innovation" loading="lazy" decoding="async" /&gt;&lt;/p&gt;
&lt;p&gt;Our mission has always been to empower people to create the perfect experience
for the technology that matters most to them. Our first product was a browser
extension because browser extensions enable permissionless innovation. You can
automate and modernize sites that you don't yourself control — whether those
are third-party websites, vendor websites, or government websites. You can also
automate and integrate across tabs. We fundamentally believe that browser
extensions are a key part of how you empower the people closest to the work to
customize their tools to the job.&lt;/p&gt;
&lt;h2&gt;Browser Extensibility Is a Spectrum&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://toddschiller.com/assets/images/ai-coding-summit/extensibility-spectrum.png" alt="Browser extensibility is a spectrum" loading="lazy" decoding="async" /&gt;&lt;/p&gt;
&lt;p&gt;Browser extensibility is in fact a spectrum. There are multiple different ways
to customize the standard browser experience.&lt;/p&gt;
&lt;p&gt;In the past year, we've even seen many different forks of the Chromium browser
for things like security or embedding AI capabilities directly into the browser.
Then you have the standard custom extension experience that most people are
familiar with — you go to the Chrome Web Store, you get an extension, it does
something.&lt;/p&gt;
&lt;p&gt;Then you have a category of extensions that enable people to customize in
different ways. Userscripts like Greasemonkey or Tampermonkey allow you to run
JavaScript in the context of a web page. PixieBrix is a different kind — you
can think of it like userscripts but more low-code or no-code, enabling a
broader audience to customize.&lt;/p&gt;
&lt;p&gt;Each of these comes with different trade-offs. The things on the right side of
the screen are generally more lightweight and more agile, but that comes with
trade-offs of less control, fewer affordances, and more restrictions versus,
for example, creating a custom extension or building your entire browser
yourself.&lt;/p&gt;
&lt;h2&gt;AI Coding Tools Aren't Designed for Browser Extension SDLC&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://toddschiller.com/assets/images/ai-coding-summit/ai-tools-gaps.png" alt="AI Coding Tools aren't designed for Browser Extension SDLC" loading="lazy" decoding="async" /&gt;&lt;/p&gt;
&lt;p&gt;As we've started applying AI coding tools like Claude and Cursor to the problem
of browser extensions, we found three main gaps in how those tools treat the
software development lifecycle:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Distributed System Architecture:&lt;/strong&gt; Browser extensions are actually
distributed systems, even though at first glance they might look like
applications.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Slow Iteration Loops + Web Store Review:&lt;/strong&gt; Because of the web store
structure and how extensions are distributed, they have slower iteration
loops than many tools designed for web applications.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Host Site Changes and Hostility:&lt;/strong&gt; Extensions often need to work in
the context of host sites that change or might be outwardly hostile to
your extension.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;These challenges are surmountable. You just have to think through how to best
handle them and how to best apply AI coding tools.&lt;/p&gt;
&lt;h2&gt;Browser Extensions Are Distributed Systems in a Box&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://toddschiller.com/assets/images/ai-coding-summit/distributed-systems.png" alt="Browser Extensions are distributed systems in a box" loading="lazy" decoding="async" /&gt;&lt;/p&gt;
&lt;p&gt;For people who aren't familiar with how browser extensions work under the hood,
I like to describe them as a distributed system in a box.&lt;/p&gt;
&lt;p&gt;A browser has multiple different tabs, each tab has multiple different frames,
and your browser extension is injecting content scripts onto each of those. But
then you also have surface areas like the side panel, as well as things working
behind the scenes — storage, the service worker, offscreen documents. A lot of
different pieces are talking to each other.&lt;/p&gt;
&lt;p&gt;You run into the usual suspects of distributed systems problems: everything is
async, you're doing message passing with serialized payloads, and you hit race
conditions. But in some ways, it's worse than a normal distributed system. Some
of these components have very complex lifecycles — tab pre-rendering,
backward/forward cache, worker recycling. It's a different animal even compared
to normal distributed systems.&lt;/p&gt;
&lt;h2&gt;Best Practice #1: Choose the Right Base Foundation&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://toddschiller.com/assets/images/ai-coding-summit/best-practice-1.png" alt="Best Practice #1: choose the right base foundation" loading="lazy" decoding="async" /&gt;&lt;/p&gt;
&lt;p&gt;The first best practice is choosing the right base foundation. I don't believe
most people should have to worry about the distributed systems issues I showed
on the last slide. You want to choose a foundation where you can be the most
productive.&lt;/p&gt;
&lt;p&gt;In our case, we started at the &lt;strong&gt;library&lt;/strong&gt; layer — we use different libraries to
smooth over quirks in the browser extension APIs for better ergonomics. But if
you're building an extension for a single, common use case, you might consider
using a &lt;strong&gt;framework&lt;/strong&gt; like WXT, Plasmo, or CRXJS to build it quickly. If you
don't want to worry about CI/CD or preview builds, you might use a &lt;strong&gt;platform&lt;/strong&gt;
like Plasmo or Shipper that handles builds automatically or even submits to the
Chrome Web Store on your behalf.&lt;/p&gt;
&lt;p&gt;On the other side, if you're building on top of extensions, the question is
whether you want to be in the &lt;strong&gt;userscript&lt;/strong&gt; world with Greasemonkey or
Tampermonkey, or build on a &lt;strong&gt;low-code platform&lt;/strong&gt; like PixieBrix that gives you
higher abstractions for integrations, compliance, and component libraries.&lt;/p&gt;
&lt;h2&gt;Best Practice #2: Maximize the &amp;quot;Boring&amp;quot; Part of the App&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://toddschiller.com/assets/images/ai-coding-summit/best-practice-2.png" alt="Best Practice #2: maximize the boring part of the app" loading="lazy" decoding="async" /&gt;&lt;/p&gt;
&lt;p&gt;Once you've chosen your foundation, the name of the game is maximizing the
boring part of the application. Browser extensions can be built with standard
frameworks like React, Angular, or Vue. What you want to do is create a big
area of those boring applications, and then encapsulate the extension-specific
code — the code that uses extension APIs, the messaging bus, etc.&lt;/p&gt;
&lt;p&gt;Use the standard isolation tactics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Dependency Injection:&lt;/strong&gt; Separate extension-specific code from standard
application logic.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Explicit &amp;gt; Implicit:&lt;/strong&gt; When using AI, it's always better to be explicit
than implicit. This helps the AI have better local context for how a
particular piece of code is working.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Enforce via Linters:&lt;/strong&gt; Enforce these boundaries using linters or other
tools to give instantaneous feedback.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Best Practice #3: Create Fast Feedback Loops&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://toddschiller.com/assets/images/ai-coding-summit/best-practice-3.png" alt="Best Practice #3: create fast feedback loops" loading="lazy" decoding="async" /&gt;&lt;/p&gt;
&lt;p&gt;Creating fast feedback loops is critical in the browser extension world. From
fastest to slowest:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Static Analysis: TypeScript + Linters&lt;/strong&gt; — Use TypeScript with type
definitions for the different browsers. We use tagged types for strings to
differentiate values. We also use lint rules extensively and use AI to create
custom lint rules. Whenever a bug comes in, we ask ourselves: was this a
preventable bug, could we have caught it earlier? Then we write a lint rule for
it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Headless Tests&lt;/strong&gt; — Take advantage of dependency injection, use mocks and
fakes like jest-webextension-mock and messaging fakes to do integration tests
in a realistic way.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Boring Component + App UI/UX Tests&lt;/strong&gt; — Use Storybook and standard Playwright
MCP for the vanilla application components.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;E2E Extension Tests&lt;/strong&gt; — Cross-browser tests via Playwright MCP. These take
longer, especially across multiple browser types and versions. We also test
against Canary. Some things that Playwright can't test, we use browser-use
tools like Reinforced QA.&lt;/p&gt;
&lt;p&gt;Always shift left — catch things earlier rather than relying on slower feedback
loops.&lt;/p&gt;
&lt;h2&gt;Best Practice #4: Create Test Pages for Host Page Quirks&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://toddschiller.com/assets/images/ai-coding-summit/best-practice-4.png" alt="Best Practice #4: create test pages for host page quirks" loading="lazy" decoding="async" /&gt;&lt;/p&gt;
&lt;p&gt;Extensions often modify a host page. It's not a good idea to run your automated
tests or do your QA against those host pages all the time because it's slow,
flaky, and in some cases they'll catch your robotic behavior. So we create test
pages for the different patterns we come across.&lt;/p&gt;
&lt;p&gt;Common quirks include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Host Style Conflicts:&lt;/strong&gt; Even with Shadow DOM, some styles can leak
through. We have a page with very extreme styles so we can see which ones
come through to our extension.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Forms + Rich Text Editors:&lt;/strong&gt; These have their own interaction models
that extensions need to handle.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Single Page Application navigation events:&lt;/strong&gt; SPAs don't trigger
standard page loads, which affects extension behavior.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Whenever you have a bug, create a test page for it. Then it's in your
repertoire of regression tests. Definitely use AI to generate those test pages.&lt;/p&gt;
&lt;h2&gt;Recap&lt;/h2&gt;
&lt;p&gt;To recap, set your AI coding tools up for success:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Choose the right foundation&lt;/li&gt;
&lt;li&gt;Maximize the boring part of the app&lt;/li&gt;
&lt;li&gt;Create fast feedback loops&lt;/li&gt;
&lt;li&gt;Create test pages for host page quirks&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That's going to give you stability and testing in your agentic loop.&lt;/p&gt;
&lt;h2&gt;Enterprise-Grade Is More Than Code&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://toddschiller.com/assets/images/ai-coding-summit/enterprise-grade.png" alt="Enterprise-Grade is more than code" loading="lazy" decoding="async" /&gt;&lt;/p&gt;
&lt;p&gt;Enterprise-grade is more than just code. You can generate all the code in the
world, but if you don't have the right enterprise IT documentation, the right
trust center, the right audits in place, you're not going to get traction in
the enterprise — especially with browser extensions. Go do those things and
definitely leverage AI for those non-code activities.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://toddschiller.com/assets/images/ai-coding-summit/pixiebrix-platform.png" alt="PixieBrix is the enterprise-grade platform for end-user AI Coding" loading="lazy" decoding="async" /&gt;&lt;/p&gt;
&lt;p&gt;As you're thinking about which foundation you want to build on, take a look at
&lt;a href="https://www.pixiebrix.com"&gt;PixieBrix&lt;/a&gt;. We are the only enterprise-grade
platform enabling end-user AI coding. Start for free and check out our workshop
to get hands-on experience building with the PixieBrix platform.&lt;/p&gt;
</content><category term="Browser Extensions"></category><category term="browser extensions"></category><category term="AI"></category><category term="speaking"></category><category term="transcript"></category></entry><entry><title>Speaking at AI Coding Summit 2026: making AI coding work for enterprise-grade browser extensions</title><link href="https://toddschiller.com/blog/ai-coding-summit-2026.html" rel="alternate"></link><published>2026-02-25T00:00:00-05:00</published><updated>2026-02-25T00:00:00-05:00</updated><author><name>Todd Schiller</name></author><id>tag:toddschiller.com,2026-02-25:/blog/ai-coding-summit-2026.html</id><summary type="html">&lt;p&gt;I'll be speaking at the
&lt;a href="https://aicodingsummit.com/"&gt;AI Coding Summit&lt;/a&gt; on February 26, 2026.&lt;/p&gt;
&lt;p&gt;My talk, &lt;strong&gt;Making AI Coding Work for Enterprise-Grade Browser Extensions&lt;/strong&gt;,
covers the unique challenges of applying AI coding workflows to browser
extension development.&lt;/p&gt;
&lt;p&gt;Mainstream &amp;quot;vibe coding&amp;quot; workflows weren't designed for browser extensions.
Extensions have distributed architectures spanning tabs …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I'll be speaking at the
&lt;a href="https://aicodingsummit.com/"&gt;AI Coding Summit&lt;/a&gt; on February 26, 2026.&lt;/p&gt;
&lt;p&gt;My talk, &lt;strong&gt;Making AI Coding Work for Enterprise-Grade Browser Extensions&lt;/strong&gt;,
covers the unique challenges of applying AI coding workflows to browser
extension development.&lt;/p&gt;
&lt;p&gt;Mainstream &amp;quot;vibe coding&amp;quot; workflows weren't designed for browser extensions.
Extensions have distributed architectures spanning tabs, background workers,
and popups; Chrome Web Store forbids remote code; and slow review cycles all
break the fast feedback loops that make AI coding productive.&lt;/p&gt;
&lt;p&gt;In the talk, I'll cover the browser customization landscape from bookmarklets
and user scripts to low-code mod platforms and AI browsers. I'll also share
practical techniques for adapting AI coding workflows to extension development
based on experience shipping extensions in enterprises around the globe.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://gitnation.com/contents/making-ai-coding-work-for-enterprise-grade-browser-extensions"&gt;Watch the talk on GitNation&lt;/a&gt;,
or read the &lt;a href="{filename}ai-coding-browser-extensions.md"&gt;full transcript&lt;/a&gt;.&lt;/p&gt;
</content><category term="Browser Extensions"></category><category term="browser extensions"></category><category term="AI"></category><category term="speaking"></category></entry><entry><title>Compliance, low-code, and the user as hero: highlights from Drata's podcast</title><link href="https://toddschiller.com/blog/compliance-uncomplicated-highlights.html" rel="alternate"></link><published>2023-03-09T00:00:00-05:00</published><updated>2023-03-09T00:00:00-05:00</updated><author><name>Todd Schiller</name></author><id>tag:toddschiller.com,2023-03-09:/blog/compliance-uncomplicated-highlights.html</id><summary type="html">Highlights from my conversation with Drata's Helina Medhin and Arlo Guthrie on Compliance Uncomplicated about low-code, AI, user experience, and SOC 2.</summary><content type="html">&lt;p&gt;I joined Helina Medhin and Arlo Guthrie (Drata's Director of Design) on
&lt;a href="https://open.spotify.com/episode/3ZPhpB28zYJe0koDAKENuj"&gt;Compliance Uncomplicated&lt;/a&gt; to talk about PixieBrix —
how low-code can democratize software customization, where humans still
beat computers, and why we went after SOC 2 early.&lt;/p&gt;
&lt;p&gt;A few highlights from the episode:&lt;/p&gt;
&lt;h2&gt;On democratizing customization&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;You should be able to customize it to your needs, even if you aren't a
programmer or a software developer.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;On software as a representation of knowledge&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[Software is] a representation of human knowledge about how the
world works. When you build tools for analyzing software or creating
software, you're actually deepening your understanding of the world
and how to create things within that. And then, when you start running
analyses or synthesizing, you're actually also creating new knowledge
as you go.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;On humans vs. computers (and AI)&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Humans and computers have different strengths. Humans are very good
at framing problems, navigating ambiguity, relationship building,
whereas computers are good at rote memory, solving large computations.
[...] [Computers are efficient at] rapidly synthesizing information
from different sources to create new information. But they still need
that interplay with humans to really drive outcome.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;On user experience&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Make the user the hero of the story. [...] To really empower them to be
the hero of their own [story] — help themselves out and help their
team members out.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;On compliance&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Compliance is about agreeing to do important things and then keeping
your word. [...] [We wanted to be] communicating our compliance and
controls in a way that our customers, and IT departments were already
familiar with. [...] It's really about keeping our word, and then also
communicating that in a way that's simple and makes sense to our
customers.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;p&gt;The full episode is on &lt;a href="https://open.spotify.com/episode/3ZPhpB28zYJe0koDAKENuj"&gt;Spotify&lt;/a&gt; and &lt;a href="https://music.amazon.com/podcasts/14e91105-cd52-4ac1-990c-88237ed605d0/episodes/a79fa01f-efc0-4107-9891-bcfbd4c0dada/compliance-uncomplicated-by-drata-compliance-uncomplicated-pixiebrix"&gt;Amazon Music&lt;/a&gt;.&lt;/p&gt;
</content><category term="Browser Extensions"></category><category term="podcast"></category><category term="pixiebrix"></category><category term="low-code"></category><category term="compliance"></category><category term="user experience"></category></entry><entry><title>Everything is UI/UX: highlights from Masters of Automation</title><link href="https://toddschiller.com/blog/masters-of-automation-2022-highlights.html" rel="alternate"></link><published>2022-08-09T00:00:00-04:00</published><updated>2022-08-09T00:00:00-04:00</updated><author><name>Todd Schiller</name></author><id>tag:toddschiller.com,2022-08-09:/blog/masters-of-automation-2022-highlights.html</id><summary type="html">A few moments from my August 2022 conversation with Alp Uguray on Masters of Automation about PixieBrix, low-code, and putting humans at the center of the future of work.</summary><content type="html">&lt;p&gt;I joined &lt;a href="https://www.linkedin.com/in/alpuguray/"&gt;Alp Uguray&lt;/a&gt; on the &lt;a href="https://open.spotify.com/episode/4bVQBI1vQiIt0AHgozcr2q"&gt;&lt;em&gt;Masters of Automation&lt;/em&gt;
podcast&lt;/a&gt; to talk about why we started PixieBrix — what
low-code/no-code really means, why the future of work has to be
human-first, and what we'd been hearing from the community. A few
moments from the conversation worth pulling out — the &lt;a href="/transcripts/masters-of-automation-2022-transcript.html"&gt;full
transcript is here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;On the spreadsheet as the original citizen-development tool&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;The spreadsheet is the most successful citizen development tool of
all time. In Microsoft Excel, you can start with static documents,
you can add styling, you can add your text, and then you can start
building out models with basic calculations. […] What people do in
the financial world is they actually record macros, they write
macros, they build out pretty much full-fledged applications in
these spreadsheets that can run entire companies.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;On everything being UI/UX&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;There's this joke that people throw around, that everything in
life is sales. […] When it comes to computers, the same is true
for UI/UX. Everything is about user interfaces. If you want to
build a game, it's about the user interface. A productivity app —
it's about the user interface. A smart device for your home — like,
I have a smart thermostat — it's about the UI/UX.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;On the future of work being human-first&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;At PixieBrix, we fundamentally believe that the future of work is
human-first. It's about putting the human in the center of it. A
lot of times when I hear people talk about future of work, they're
coming at it from a view of a particular SaaS app, like a CRM, or
a particular business process or process management platform […].
But what it really comes down to is that each person actually has
multiple areas of responsibility. They're participants in multiple
processes, multiple different functions in the business.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;On not loving chat interfaces&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;I know a lot of people like using chat interfaces to AI, or other
things. I actually can't stand that. I prefer to have a different
sort of interface — clicking, seeing multiple options — instead of
engaging in a conversation. It's really about being able to craft
the perfect experience for yourself, regardless of what enabling
technologies you're using under the hood.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;On embracing imperfection because the human is in the loop&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Because the human is in the loop, you don't need to be perfect.
You don't need something that you write once and then it's going
to go off and run on its own for two years, five years, ten years.
You can really embrace that imperfection and really embrace that
personalization, based on what that individual is, or what that
business unit values, as part of the process.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;On empowerment and the safety net&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;What's really interesting about empowering is, it's about giving
those capabilities, but it's also about having a good safety net.
Making it so that it's okay to make mistakes, it's okay to
experiment. You're not accidentally sending out thousands of emails
if you mess up a line of code, or you're not accidentally breaking
compliance rules. […] It's about combining that sort of safety net
with also those incredible creator tools, for people to really
express themselves and experiment.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;On low floor, high ceiling&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;We really try to make it so that there's a low floor to starting
to use us, but a high ceiling to what's possible. And then really
trying to create a welcoming community for people to get inspired,
get help extremely quickly, and really showcase what they've
created.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;p&gt;The full episode is on &lt;a href="https://open.spotify.com/episode/4bVQBI1vQiIt0AHgozcr2q"&gt;Spotify&lt;/a&gt;, &lt;a href="https://podcasts.apple.com/us/podcast/004-a-low-code-platform-enables-you-to-change-of/id1622678474?i=1000575520642"&gt;Apple Podcasts&lt;/a&gt;,
and the &lt;a href="https://www.themasters.ai/episodes/interview-todd-schiller-pixiebrix"&gt;show's website&lt;/a&gt;. A lightly edited &lt;a href="/transcripts/masters-of-automation-2022-transcript.html"&gt;transcript is on
this site&lt;/a&gt;.&lt;/p&gt;
</content><category term="Browser Extensions"></category><category term="podcast"></category><category term="pixiebrix"></category><category term="low-code"></category><category term="user experience"></category><category term="browser extensions"></category><category term="automation"></category></entry><entry><title>A brief history of browser extensibility</title><link href="https://toddschiller.com/blog/history-browser-extensibility.html" rel="alternate"></link><published>2021-03-04T00:00:00-05:00</published><updated>2021-03-04T00:00:00-05:00</updated><author><name>Todd Schiller</name></author><id>tag:toddschiller.com,2021-03-04:/blog/history-browser-extensibility.html</id><summary type="html">&lt;p&gt;Web browsers are the modern operating system.
Google's &lt;a href="https://en.wikipedia.org/wiki/Chrome_OS"&gt;Chrome OS&lt;/a&gt;, an operating
system that runs the Chrome Browser
exclusively, &lt;a href="https://arstechnica.com/gadgets/2021/02/the-worlds-second-most-popular-desktop-operating-system-isnt-macos-anymore/"&gt;is now the second most popular desktop operating system&lt;/a&gt;.
Additionally, the rise of &lt;a href="https://www.electronjs.org/"&gt;Electron&lt;/a&gt; and similar
cross-platform frameworks means that many of the applications we use every day
(e.g., Slack …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Web browsers are the modern operating system.
Google's &lt;a href="https://en.wikipedia.org/wiki/Chrome_OS"&gt;Chrome OS&lt;/a&gt;, an operating
system that runs the Chrome Browser
exclusively, &lt;a href="https://arstechnica.com/gadgets/2021/02/the-worlds-second-most-popular-desktop-operating-system-isnt-macos-anymore/"&gt;is now the second most popular desktop operating system&lt;/a&gt;.
Additionally, the rise of &lt;a href="https://www.electronjs.org/"&gt;Electron&lt;/a&gt; and similar
cross-platform frameworks means that many of the applications we use every day
(e.g., Slack) are actually browser applications in disguise.&lt;/p&gt;
&lt;p&gt;The ubiquity of browsers has cemented their critical role in consumer
productivity and self-efficacy. Given this importance, I wanted to learn how
browser extensibility has evolved over the years. Since I couldn't
find &lt;a href="https://en.wikipedia.org/wiki/Browser_extension"&gt;a complete history&lt;/a&gt;, I
set out to write my own.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Disclaimer #1: I'm going to gloss over many details to keep this post as
non-technical as possible. I won't, for example, dig into the distinction
between a browser and a rendering engine.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Disclaimer #2: If I make any mistakes or omit anything you feel is important,
please post in the comments. As I have not personally had much exposure to
browsers in non-English-speaking countries, there will undoubtedly be gaps in
this post.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Consumer Web Browsers (1993-)&lt;/h2&gt;
&lt;p&gt;In the beginning, there was Tim Berners-Lee's WorldWideWeb browser (1990).
However, Marc Andreessen's Mosaic (1993) was the first browser to gain
mainstream popularity, as it
was &lt;a href="https://www.w3.org/People/Berners-Lee/FAQ.html#browser"&gt;easy to install, displayed graphics, and provided customer support&lt;/a&gt;.
Andreessen then went on to launch Netscape, which would battle Internet
Explorer (first released in 1995) for dominance in the browser wars of the
late '90s. The browser wars culminated with Microsoft
facing &lt;a href="https://en.wikipedia.org/wiki/United_States_v._Microsoft_Corp."&gt;antitrust action over its integration of Internet Explorer and Windows&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="/assets/images/browser-extensibility/mosaic-mac.webp" alt="Mosaic for Mac" loading="lazy" decoding="async" /&gt;&lt;/p&gt;
&lt;h2&gt;Plug-ins (1995-2015ish)&lt;/h2&gt;
&lt;p&gt;The early web only supported static content. Plug-ins -- native executables that
render dynamic content in the browser -- opened the door for multimedia,
enterprise applications, and gaming. Internet Explorer supported plug-ins via
its &lt;a href="https://en.wikipedia.org/wiki/ActiveX"&gt;ActiveX&lt;/a&gt; technology,
while &lt;a href="https://en.wikipedia.org/wiki/NPAPI"&gt;other browsers supported NPAPI&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;On the consumer side, you basically couldn't browse the web without installing
the Flash (1996) and Java (1998) plug-ins.&lt;/p&gt;
&lt;p&gt;&lt;img src="/assets/images/browser-extensibility/flash-player.webp" alt="Flash Player website in March 2006 via the Internet Archive" loading="lazy" decoding="async" /&gt;&lt;/p&gt;
&lt;p&gt;As native executables, plug-ins were a chronic source of security
vulnerabilities and instability. Despite these problems, it wasn't until the
adoption of the HTML5 standard and modern Web APIs in 2014 that browsers could
rid themselves of plug-ins without sacrificing functionality.&lt;/p&gt;
&lt;p&gt;Desktop browsers began dropping support for plug-ins in 2015,
with &lt;a href="https://en.wikipedia.org/wiki/NPAPI#Support/deprecation"&gt;Chrome leading the charge&lt;/a&gt;.
On mobile, iOS (2003)
famously &lt;a href="https://web.archive.org/web/20200430094807/https://www.apple.com/hotnews/thoughts-on-flash/"&gt;did not offer Flash from the outset&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The Flash plug-in has been the longest holdout due to the ubiquity of Flash
content. Firefox, for instance, refused to drop support for Flash until the
beginning of this year.&lt;/p&gt;
&lt;h2&gt;User Style Sheets (1998-2019ish)&lt;/h2&gt;
&lt;p&gt;With multiple browsers on the market and growing incompatibilities in site
rendering, Tim Berners-Lee founded the World Wide Web Consortium (W3C) in 1994
to develop cross-browser standards.&lt;/p&gt;
&lt;p&gt;One of these standards was 1996's Cascading Style Sheets (CSS) specification.
CSS defines support for &amp;quot;stylesheets&amp;quot; that tell a browser how to style the
content on a page. Stylesheets can come from three different sources:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Document author: The author of the website&lt;/li&gt;
&lt;li&gt;User: The user of the browser&lt;/li&gt;
&lt;li&gt;User agent: The default style for the user agent (i.e., the browser)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="/assets/images/browser-extensibility/css1-test-suite.webp" alt="Part of the CSS 1 test suite" loading="lazy" decoding="async" /&gt;&lt;/p&gt;
&lt;p&gt;In 1998, Opera 3.5 was the first browser to support CSS and
included &lt;a href="https://dbaron.org/css/user/"&gt;support for user stylesheets&lt;/a&gt;. Internet
Explorer followed suit with the release of Internet Explorer 4 in 1999.&lt;/p&gt;
&lt;p&gt;In 2005, with the introduction of Jason
Barnabe's &lt;a href="https://en.wikipedia.org/wiki/Stylish"&gt;Stylish extension&lt;/a&gt;, consumer
styling began shifting to style manager browser extensions. Compared to user
stylesheets, style managers offer more flexible controls, sharing features, and
ease of use.&lt;/p&gt;
&lt;p&gt;Chrome dropped support for user stylesheets in 2014. Firefox still has support
via the &lt;code&gt;userContent.css&lt;/code&gt;
stylesheet,
&lt;a href="https://en.wikipedia.org/wiki/Stylish"&gt;but the user stylesheet hasn't been accessible from the main interface since 2019&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;**Correction 6/5/2021: Safari still supports a user stylesheet from the Advanced
tab in preferences. Here's
a &lt;a href="http://theoveranalyzed.net/2018/3/16/safaris-custom-style-sheet"&gt;history of custom stylesheets in Safari&lt;/a&gt;.
**&lt;/p&gt;
&lt;h2&gt;Bookmarklets (1998-)&lt;/h2&gt;
&lt;p&gt;Brendan Eich and
Netscape &lt;a href="https://en.wikipedia.org/wiki/JavaScript#Creation_at_Netscape"&gt;introduced JavaScript to the world in 1995&lt;/a&gt;.
JavaScript is a programming language that website authors can include to create
dynamic content and interactive user interfaces.&lt;/p&gt;
&lt;p&gt;JavaScript also opened up a new avenue for browser customization:
bookmarklets. &lt;a href="https://en.wikipedia.org/wiki/Bookmarklet"&gt;Bookmarklets&lt;/a&gt; are
small snippets of JavaScript contained in a bookmark that starts with
&lt;code&gt;javascript:&lt;/code&gt;. When you click the bookmark, it runs the JavaScript in the
context of the page, possibly loading more JavaScript from another source.&lt;/p&gt;
&lt;p&gt;For example, here's a bookmarklet that opens the historical versions of a page
in the Internet Archive's &lt;a href="https://archive.org/web/"&gt;Wayback Machine&lt;/a&gt;:&lt;/p&gt;
&lt;!-- markdownlint-disable MD013 --&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;javascript&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="ow"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://web.archive.org/web/*/&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/\/$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;!-- markdownlint-enable MD013 --&gt;
&lt;p&gt;Bookmarklets hit a road bump in 2004 with the introduction of W3C's Content
Security Policy (CSP) specification. Designed to protect sites against malicious
user content and third party code, CSP enabled site owners to lock-down code
origins and network requests.&lt;/p&gt;
&lt;p&gt;However, wanting to maintain extensibility,
the &lt;a href="https://www.w3.org/TR/CSP3/#extensions"&gt;CSP Specification&lt;/a&gt; specifies that
bookmarklets and other extensibility features should be exempt from CSP
restrictions:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Policy enforced on a resource SHOULD NOT interfere with the operation of
user-agent features like addons, extensions, or bookmarklets. These kinds of
features generally advance the user's priority over page authors, as espoused
in [HTML-DESIGN].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In practice, things are different. In
Firefox, &lt;a href="https://bugzilla.mozilla.org/show_bug.cgi?id=866522"&gt;bookmarklets are subject to the site's CSP&lt;/a&gt;,
preventing bookmarklets from being run on many pages. In Chrome, bookmarklets
aren't subject to the CSP. But, by default, Chrome only shows bookmarks on the
New Tabs page; users must configure the bookmarks bar to be shown on web pages.&lt;/p&gt;
&lt;h2&gt;Browser Extensions (1999-)&lt;/h2&gt;
&lt;p&gt;The modern approach to browser extensibility, browser extensions, kicked off
in &lt;a href="https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/aa753620(v=vs.85)"&gt;1999 when Internet Explorer added support in Internet Explorer 4&lt;/a&gt;.
The new extensions supported &amp;quot;Explorer Bars&amp;quot; (i.e., the toolbar) and added
entries to context menus.&lt;/p&gt;
&lt;p&gt;Browser extensions, also sometimes called add-ons, support modification of the
browser interface, modification of a webpage's user interface, and running
JavaScript in the context of a webpage. These days, browser extensions support a
wide variety of use cases ranging
from &lt;a href="https://chrome.google.com/webstore/detail/grammarly-for-chrome/kbfnbcaeplbcioakkpcpgfkobkghlhen"&gt;writing assistance&lt;/a&gt;
and &lt;a href="https://chrome.google.com/webstore/detail/honey/bmnlcjabgnpnenekpadlanbbkooimhnj"&gt;web coupons&lt;/a&gt;
to &lt;a href="https://chrome.google.com/webstore/detail/ublock-origin/cjpalhdlnbpafiamejdnhcphjbkeiagm"&gt;adblocking&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Firefox launched with extension support in
2004, &lt;a href="https://en.wikipedia.org/wiki/Browser_extension#History"&gt;with the other mainstream browsers following suit in 2009-2010&lt;/a&gt;.
As early as 2005, the &amp;quot;Mozilla Update&amp;quot; page was bustling with activity:&lt;/p&gt;
&lt;p&gt;&lt;img src="/assets/images/browser-extensibility/mozilla-addons.webp" alt="addons.mozilla.org in March 2005 via the Internet Archive" loading="lazy" decoding="async" /&gt;&lt;/p&gt;
&lt;p&gt;The major browser vendors each maintain their own browser extension marketplace
where extensions undergo a security review and updates can be automatically
distributed to users.&lt;/p&gt;
&lt;p&gt;Unfortunately, because browser extensions have access to a user's browsing
activity, they're an attractive attack vector for malicious actors.&lt;/p&gt;
&lt;p&gt;To curb abuse, Chrome, in particular, has taken steps to restrict how browser
extensions are distributed. In 2014, Chrome introduced restrictions so
that &lt;a href="https://techcrunch.com/2014/05/27/chrome-for-windows-will-now-only-install-extensions-from-googles-web-store/"&gt;Windows users could only install extensions from the Chrome Web Store&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Mozilla XUL (1997-2017)&lt;/h2&gt;
&lt;p&gt;Mozilla and Firefox's user interface was implemented using a language called
XUL, the XML User Interface Language. The XUL defines a user interface that is
rendered on the fly.&lt;/p&gt;
&lt;p&gt;In Firefox, browser extensions could modify the XUL, thus allowing them to
change core parts of the user interface (e.g., adding toolbars).&lt;/p&gt;
&lt;p&gt;Firefox discontinued XUL for extensions in 2017 because it led to
vulnerabilities and instability when multiple extensions modified the UX.
Technical users can still make limited modifications to the Firefox interface
via its &lt;a href="https://www.userchrome.org/"&gt;userChrome.css settings file&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Alternative Browser Distributions (2004-)&lt;/h2&gt;
&lt;p&gt;The Netscape browser was open-sourced in 1998 and used to build the Mozilla
browser suite. &lt;a href="https://en.wikipedia.org/wiki/Firefox"&gt;Mozilla Firefox&lt;/a&gt;, the
first mainstream open-source browser, was subsequently released in 2004.&lt;/p&gt;
&lt;p&gt;In 2008, Google released the Chromium open-source browser project. Google uses
Chromium as the basis for its Chrome web browser. By being open-source,
developers can &amp;quot;fork&amp;quot; the browser, modify it, and distribute their own custom
versions.&lt;/p&gt;
&lt;p&gt;There have been a lot
of &lt;a href="https://en.wikipedia.org/wiki/List_of_web_browsers"&gt;alternative browsers&lt;/a&gt;,
many of which have not stood the test of time. Some notable examples include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GNU IceWeasel/IceCat (2005-): Firefox distribution without proprietary
plug-ins.&lt;/li&gt;
&lt;li&gt;Flock (2005-2011): Browser with integrated social media features. Originally
based on Firefox and later based on Chromium. Was purchased by Zynga, a social
games company.&lt;/li&gt;
&lt;li&gt;Tor Browser Bundle (2008-): Firefox distribution that makes use of the Tor
network for private browsing.&lt;/li&gt;
&lt;li&gt;Brave (2016-): Brendan Eich's (of JavaScript fame) Chromium browser with
content-creator revenue sharing via the Basic Attention Token cryptocurrency.&lt;/li&gt;
&lt;li&gt;Microsoft Edge (2019-): Microsoft's Chromium-based browser after their
in-house browser (also called Edge) failed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="/assets/images/browser-extensibility/flock-browser.webp" alt="The Flock browser had social media features built in" loading="lazy" decoding="async" /&gt;&lt;/p&gt;
&lt;h2&gt;Userscripts (2005-)&lt;/h2&gt;
&lt;p&gt;Userscripts are small scripts that modify a webpage. Like browser extensions,
they're typically written in JavaScript. However, userscripts are lighter weight
in that you don't have to package them and distribute them through the browser's
web store. Instead, to run and manage userscripts, you use a browser extension
aptly called a userscript manager.&lt;/p&gt;
&lt;p&gt;Userscripts provide many benefits over bookmarklets: they can run automatically
on a page, aren't subject to length restrictions, and can run outside of a
site's CSP.&lt;/p&gt;
&lt;p&gt;The first userscript
manager, &lt;a href="https://en.wikipedia.org/wiki/Greasemonkey"&gt;Greasemonkey&lt;/a&gt;, was
released for Firefox in 2005. There are now several variations to choose from,
including Tampermonkey (2010) and Violentmonkey (2013). These variations all use
the same Greasemonkey-style scripts.&lt;/p&gt;
&lt;p&gt;In 2019, Firefox
added &lt;a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/userScripts"&gt;formal support for userscripts&lt;/a&gt;
in extensions, providing additional security guarantees. Chromium has a separate
sandbox feature to run code in isolation, but has no formal userscript support.
However, as I will touch on a bit later, the end is nigh for userscripts in
Chromium browsers.&lt;/p&gt;
&lt;h2&gt;Converging on the WebExtensions API (2017-)&lt;/h2&gt;
&lt;p&gt;Abandoning XUL-based extensions in 2017, Firefox switched to the defacto
standard WebExtensions API by and large mimicking Chromium's API.&lt;/p&gt;
&lt;p&gt;Firefox's shift means that the vast majority of browser extensions are now using
the APIs defined by Chrome's Manifest Version 2, first released way back in
2012 (although additional incremental API functionality has been introduced
since then).&lt;/p&gt;
&lt;p&gt;Due to its waning market share, Firefox chose to support the &lt;code&gt;chrome&lt;/code&gt; API
namespace in addition to the vendor-agnostic &lt;code&gt;browser&lt;/code&gt; API namespace. In many
cases, Chrome extensions can be ported over to Firefox with no code
modifications. Firefox also
provides
&lt;a href="https://github.com/mozilla/webextension-polyfill"&gt;a polyfill for developers to make their vendor-agnostic code compatible with Chrome&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Writing extensions for Safari traditionally
involved &lt;a href="https://developer.apple.com/documentation/safariservices/safari_app_extensions/building_a_safari_app_extension"&gt;writing them using Apple's XCode&lt;/a&gt;
development tools. Starting with Safari 14 in 2020, Safari
also &lt;a href="https://developer.apple.com/news/?id=kuswih5l"&gt;adopted the WebExtensions API&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Manifest V3 (2021-)&lt;/h2&gt;
&lt;p&gt;In January 2021, Chrome released its controversial Manifest Version 3 (V3) of
the WebExtensions APIs. &amp;quot;Manifest&amp;quot; refers to both the technical integration
points and the policies Chrome enforces when reviewing extensions for its Chrome
Web Store.&lt;/p&gt;
&lt;p&gt;In line with previous browser API changes, the stated goals of Manifest V3 are
security, speed, and reliability. The major changes introduced in Manifest V3
are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Making network request modification declarative (speed/security)&lt;/li&gt;
&lt;li&gt;Using service workers for background operations, vs. a persistent background
page (reliability)&lt;/li&gt;
&lt;li&gt;Banning remotely hosted JavaScript code, a common vector of security
vulnerabilities (security)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Manifest V3
is &lt;a href="https://en.wikipedia.org/wiki/Google_Chrome#Reception"&gt;controversial in the browser extension community&lt;/a&gt;
because it breaks the functionality of many popular extensions.&lt;/p&gt;
&lt;p&gt;For the general public, the most worrisome is that popular content and
adblockers (e.g., uBlock Origin and Privacy Badger) will no longer be supported.
These blockers work by maintaining a large ruleset to block network requests.
Such large and dynamic rulesets are not supported in Manifest V3. (To be fair,
in response to the outrage, the Chrome team has increased the number of rules
per extension.)&lt;/p&gt;
&lt;p&gt;In addition, userscript managers, by their very definition, work by allowing
users to install and run remote JavaScript code. As of
now,
&lt;a href="https://github.com/violentmonkey/violentmonkey/issues/505"&gt;even user-pasted code will not be supported by Chrome once support for Manifest V2 is removed (likely in 2022)&lt;/a&gt;
.&lt;/p&gt;
&lt;h2&gt;The Future: No/Low-Code Browser Extension Builders (2021-)&lt;/h2&gt;
&lt;p&gt;Ever since mainstream browsers added support for extensions, the developer
community has built an incredible ecosystem of extensions that gives consumers
control over their browsing experience:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Style managers (2005-): Customize style of a page.&lt;/li&gt;
&lt;li&gt;Userscript managers (2005-2022?): Run custom code automatically.&lt;/li&gt;
&lt;li&gt;Content/adblockers (&lt;a href="https://en.wikipedia.org/wiki/Adblock_Plus#Background"&gt;2002&lt;/a&gt;-):
Block network requests and elements.&lt;/li&gt;
&lt;li&gt;Context menu managers (?-): Add context menu items (e.g., opening a new URL
dynamically based on the selected text).&lt;/li&gt;
&lt;li&gt;Web clippers (?-): Clip information from a page; customize what's clipped and
where it's sent.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Despite the number of extensions
published (&lt;a href="https://kommandotech.com/statistics/chrome-usage-statistics/"&gt;the Chrome Web Store has 189K extensions&lt;/a&gt;),
there is still a lot of room to innovate. In particular, the pairing of advances
in no-code + API-fication with browser extensions has the potential to increase
consumer productivity and self-efficacy dramatically.&lt;/p&gt;
&lt;p&gt;To realize this vision, I founded &lt;a href="https://www.pixiebrix.com/"&gt;PixieBrix&lt;/a&gt;. At
PixieBrix, we are working to create an open-source browser extension for
consumers and companies to customize their browsing experience to fit the way
they work. PixieBrix is no/low-code -- you configure and combine &amp;quot;bricks&amp;quot; to add
missing functionality, integrate sites without APIs, and automate repetitive
tasks.&lt;/p&gt;
&lt;p&gt;Personally, I'm very excited by the surge of interest in this space. One notable
startup I've come across is &lt;a href="https://insightbrowser.com/"&gt;Insight Browser&lt;/a&gt;, an
iOS browser that allows users to build and share simple extensions. In the
enterprise world, &lt;a href="https://extension.dev/"&gt;Extension.dev&lt;/a&gt; is another low-code
builder for internal enterprise extensions (check out their
comprehensive &amp;quot;&lt;a href="https://public.extension.dev/what-internal-extensions-are-people-building"&gt;What internal extensions are people building?&lt;/a&gt;&amp;quot;
page).&lt;/p&gt;
&lt;p&gt;If you're interested in working or investing in this space,
I'd &lt;a href="mailto:todd@pixiebrix.com"&gt;love to hear from you&lt;/a&gt;. Want to follow us on our
journey?
&lt;a href="https://twitter.com/pixiebrix"&gt;Follow us on Twitter&lt;/a&gt;,
&lt;a href="https://github.com/pixiebrix/pixiebrix-extension/"&gt;star our GitHub repository&lt;/a&gt;,
or &lt;a href="https://www.pixiebrix.com/"&gt;subscribe to our newsletter&lt;/a&gt;.&lt;/p&gt;
</content><category term="Browser Extensions"></category><category term="web browsers"></category><category term="browser extensions"></category><category term="history"></category></entry><entry><title>7 bite-sized tips for reliable web automation and scraping selectors</title><link href="https://toddschiller.com/blog/reliable-web-scrapers.html" rel="alternate"></link><published>2020-12-03T00:00:00-05:00</published><updated>2020-12-03T00:00:00-05:00</updated><author><name>Todd Schiller</name></author><id>tag:toddschiller.com,2020-12-03:/blog/reliable-web-scrapers.html</id><summary type="html">&lt;p&gt;If you're like most developers, you've probably
encountered &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors"&gt;Cascading Style Sheet (CSS) selectors&lt;/a&gt;
for styling webpages.&lt;/p&gt;
&lt;p&gt;For example, the following CSS rule combines a paragraph element selector &lt;code&gt;p&lt;/code&gt;
with a class name selector &lt;code&gt;.lead&lt;/code&gt; to set the font size:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;lead&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="k"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="kt"&gt;px&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;We can mix-and-match CSS selectors to …&lt;/p&gt;</summary><content type="html">&lt;p&gt;If you're like most developers, you've probably
encountered &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors"&gt;Cascading Style Sheet (CSS) selectors&lt;/a&gt;
for styling webpages.&lt;/p&gt;
&lt;p&gt;For example, the following CSS rule combines a paragraph element selector &lt;code&gt;p&lt;/code&gt;
with a class name selector &lt;code&gt;.lead&lt;/code&gt; to set the font size:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;lead&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="k"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="kt"&gt;px&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;We can mix-and-match CSS selectors to describe any subset of elements on a page.
There are CSS selectors for HTML tag types, ids, classes, attributes, page
structure, and even UX interactions.&lt;/p&gt;
&lt;p&gt;Because of their expressiveness, CSS selectors are used everywhere in the web
ecosystem:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Web
Applications (&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector"&gt;Web API&lt;/a&gt;,
&lt;a href="https://api.jquery.com/category/selectors/"&gt;JQuery&lt;/a&gt;):
modifying the DOM, attaching event handlers, etc.&lt;/li&gt;
&lt;li&gt;Automated Testing: finding elements to interact with, checking assertions,
etc.&lt;/li&gt;
&lt;li&gt;Web Scraping: selecting data to extract, finding links to traverse, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Over at PixieBrix, selectors are an integral part
of &lt;a href="https://www.pixiebrix.com/"&gt;our web customization engine&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Unfortunately, if we don't control a web page, writing reliable selectors can be
an unpleasant experience. Some old-school WYSIWYG-built sites (I'm looking at
you, &lt;a href="https://en.wikipedia.org/wiki/Microsoft_FrontPage"&gt;Microsoft FrontPage&lt;/a&gt;)
and modern Single Page Applications (SPAs) can be downright hostile to work
with.&lt;/p&gt;
&lt;p&gt;To help folks out, I've compiled a set of tips I find useful when writing
selectors for enhancing, automating, and scraping 3rd-party sites.&lt;/p&gt;
&lt;h2&gt;Tip #0: Use JQuery extensions&lt;/h2&gt;
&lt;p&gt;When working in a browser context (normal or headless),
use &lt;a href="https://api.jquery.com/category/selectors/jquery-selector-extensions/"&gt;JQuery's selector extensions&lt;/a&gt;.
Selectors such as &lt;code&gt;:eq&lt;/code&gt; , &lt;code&gt;:header&lt;/code&gt; , and &lt;code&gt;:contains&lt;/code&gt; make many selections
significantly more straightforward.&lt;/p&gt;
&lt;p&gt;A downside is that JQuery extensions are slower because they can't leverage the
browser's native CSS evaluation. However, for automation and scraping, you
probably won't ever notice. If it does become a problem, there are
simple &lt;a href="https://learn.jquery.com/performance/optimize-selectors/"&gt;techniques to optimize JQuery selectors&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In addition to CSS+JQuery, it's also helpful to learn a bit
of &lt;a href="https://developer.mozilla.org/en-US/docs/Web/XPath"&gt;XPath&lt;/a&gt;, an XML/HTML
traversal language that browsers support natively.&lt;/p&gt;
&lt;h2&gt;Tip #1: Avoid structure-based selectors&lt;/h2&gt;
&lt;p&gt;Browsers and some &lt;a href="https://github.com/fczbkk/css-selector-generator"&gt;libraries&lt;/a&gt;
are able to automatically generate unique CSS selectors for elements.&lt;/p&gt;
&lt;p&gt;&lt;img src="/assets/images/web-selectors/chrome-css-selector.webp" alt="Generating a unique CSS selector for an element in Chrome" loading="lazy" decoding="async" /&gt;&lt;/p&gt;
&lt;p&gt;Automatic generators are great because they can take advantage of the structure
of the Document Object Model (DOM). Automatic generators are also terrible
because they sometimes rely too much on the structure of the DOM.&lt;/p&gt;
&lt;p&gt;Recently, a tool gave me the following selector:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;nth-child&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;5&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;nth-child&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;2&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;nth-child&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;2&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;nth-child&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;nth-child&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;3&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Structural selectors like these are extremely sensitive to small changes on the
page, including non-visible changes. (For example: an element with
&lt;code&gt;display:none&lt;/code&gt; could be inserted before an element.)&lt;/p&gt;
&lt;p&gt;These selectors will silently return the wrong element, and we'll wind up
debugging a failed automated test, or ingesting bad extracted data.&lt;/p&gt;
&lt;p&gt;Therefore, whenever possible, I eschew overly-specific structural selectors in
favor of ids, class names, attributes, and textual content.&lt;/p&gt;
&lt;h2&gt;Tip #2: Avoid dynamically generated attributes&lt;/h2&gt;
&lt;p&gt;In modern Single Page Applications (SPAs), element ids and class names are
commonly dynamically generated.&lt;/p&gt;
&lt;p&gt;Dynamic class names are computed when the code is compiled/bundled. That's
because the class names in the HTML file must match the names in separate CSS
stylesheets. Dynamic class names will change whenever the site is updated. (
Unless they are computed deterministically, e.g., using a hash. Then they'll
change whenever the style is changed).&lt;/p&gt;
&lt;p&gt;For example, take the &amp;quot;Google Search&amp;quot; button on
the &lt;a href="https://www.google.com"&gt;Google homepage&lt;/a&gt;. It's built
with &lt;a href="https://developers.google.com/closure"&gt;Google's Closure Framework&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;gNO89b&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Google Search&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;btnK&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;submit&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;At first glance, the &lt;code&gt;name=&amp;quot;btnK&amp;quot;&lt;/code&gt; also looks random, but it's not. The &amp;quot;I'm
Feeling Lucky&amp;quot; button button on the page has the name &lt;code&gt;btnI&lt;/code&gt;, suggesting they're
human-picked. As a rule of thumb, &lt;code&gt;input&lt;/code&gt; name attributes can't be dynamic
because other parts of the application (either front-end, or backend) depend on
them.&lt;/p&gt;
&lt;p&gt;A common source of dynamic class names in applications is the use
of &lt;a href="https://css-tricks.com/css-modules-part-1-need/"&gt;CSS Modules&lt;/a&gt;. CSS Modules
automatically encapsulating styles to avoid accidental styling collisions.
Here's an example header:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;_styles__title_309571057&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;An example heading&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;While we can't match the exact class name, it will follow a consistent naming
pattern across updates of the site. Therefore, we can match on the pattern
using &lt;a href="https://www.w3schools.com/css/css_attribute_selectors.asp"&gt;CSS's starts-with attribute selector&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;class&lt;/span&gt;&lt;span class="o"&gt;^=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;_styles__title_&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Dynamic ids, on the other hand, are managed by the SPA framework at runtime.
Therefore, they'll change for each run of the application. Take, for example,
the profile header from LinkedIn, which uses
the &lt;a href="https://emberjs.com/"&gt;Ember.js&lt;/a&gt; framework:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;section&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;ember1398&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;pv-top-card artdeco-card ember-view&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="cm"&gt;&amp;lt;!-- more elements --&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;section&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here, the ember1398 id isn't random — the framework sequentially generates a new
id for each element it renders. In practice, the id of an element will differ
between page loads because elements don't always load in the exact same order.&lt;/p&gt;
&lt;h2&gt;Tip #3: Search text with JQuery's &lt;code&gt;:contains&lt;/code&gt; selector&lt;/h2&gt;
&lt;p&gt;JQuery's &lt;a href="https://api.jquery.com/contains-selector/"&gt;contains selector&lt;/a&gt; selects
elements that contain the given text anywhere within them. The selector is not
available in plain CSS, but it is available as
an &lt;a href="https://developer.mozilla.org/en-US/docs/Web/XPath/Functions/contains"&gt;XPath function&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For example, consider the following HTML:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Lorem ipsum dolor sit amet, consectetur ...&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We could select this element with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Lorem ipsum&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If there are other tags, we need to add additional selectors to clarify which
element we want. For example, the above selector would select the &lt;code&gt;div&lt;/code&gt;, &lt;code&gt;p&lt;/code&gt;,
and &lt;code&gt;b&lt;/code&gt; elements in this document:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;b&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Lorem ipsum&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;b&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; dolor sit amet, consectetur...&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To select just the &lt;code&gt;div&lt;/code&gt;, we'd need to specify the tag in the selector:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Lorem ipsum&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In some cases, the text might contain HTML entities, e.g., a non-breaking space:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Lorem&lt;span class="ni"&gt;&amp;amp;nsbp;&lt;/span&gt;ipsum&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Trying to select &amp;quot;Lorem ipsum&amp;quot; won't match here. In these cases, we provide the
contains selector multiple times to match only elements that contain both words:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Lorem&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ipsum&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Finally, a caveat/warning: &lt;em&gt;when writing text selectors, be aware of which text
is translated when the page is viewed in a different language.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Tip #4: Target selection with &lt;code&gt;:has&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Sometimes we need more precision in targeting a text search. In these cases we
can combine a &lt;code&gt;:contains&lt;/code&gt; selector within a CSS &lt;code&gt;:has&lt;/code&gt; selector. Suppose we
wanted to get the year a property was built
from &lt;a href="https://www.apartments.com/the-max-new-york-ny/24f34qb/"&gt;Apartments.com&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;specList&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h3&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Property Information&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h3&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
         &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;bullet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;•&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Built in 2018&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
         &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;bullet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;•&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;1016 Units/44 Stories&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We can find the div with the Property Information header, and then grab the list
item corresponding to the year the property was built:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;specList&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;h3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Property Information&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Built&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;:has&lt;/code&gt; selector can also be used to find the containing element. The
following selector matches a list containing an item with &amp;quot;Built&amp;quot;, rather than
the individual list item:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;has&lt;/span&gt;&lt;span class="o"&gt;(&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Built&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Tip #5: Use ARIA attributes for more readable selectors&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA"&gt;Accessible Rich Internet Applications (ARIA)&lt;/a&gt;
attributes support the accessibility of a site, e.g., for providing context to
screen readers. Using them for selectors also makes selectors more readable.&lt;/p&gt;
&lt;p&gt;For example, the &lt;code&gt;aria-label&lt;/code&gt; attribute labels elements that otherwise would
depend on visual cues:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;aria-label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Close&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;onclick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;myDialog.close()&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;X&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;These can be selected against using standard
CSS &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors"&gt;attribute selectors&lt;/a&gt;,
e.g.:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;aria-label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Close&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;One thing to watch out for with the &lt;code&gt;aria-label&lt;/code&gt; attribute is that it's always
internationalized/translated alongside other UX text.&lt;/p&gt;
&lt;p&gt;ARIA attributes also provide information about the role of elements on the
page (e.g.,
the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/button_role"&gt;role attribute&lt;/a&gt;)
and even the relationship between elements on the page (
e.g., &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-labelledby_attribute"&gt;aria-labelledby&lt;/a&gt;).&lt;/p&gt;
&lt;h2&gt;Tip #6: Be on the lookout for automated testing attributes&lt;/h2&gt;
&lt;p&gt;Developers using automated testing frameworks, e.g.
Cypress, &lt;a href="https://docs.cypress.io/guides/references/best-practices.html"&gt;add data attributes to make their life easier when writing automated tests&lt;/a&gt;.
Sometimes these attributes will find their way to the production site:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;data-test-id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;content&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="cm"&gt;&amp;lt;!-- some content --&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;data-cy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;content&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="cm"&gt;&amp;lt;!-- some content --&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Testing attributes can be selected just like any other attribute, and are often
unique (just like an id):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;data-cy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;content&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src="/assets/images/web-selectors/together.webp" alt="We're all in this together" loading="lazy" decoding="async" /&gt;&lt;/p&gt;
&lt;h2&gt;Bonus Tip: handle flat key/value pairs with the adjacent sibling combinator&lt;/h2&gt;
&lt;p&gt;The &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Adjacent_sibling_combinator"&gt;adjacent sibling combinator (+)&lt;/a&gt;
matches the element immediately following another element.&lt;/p&gt;
&lt;p&gt;Supposed we have the following HTML, structured as multiple key-value pairs per
row/list item:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;row&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;k&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Name:&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;v&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Laura Smith&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;k&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Year:&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;v&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;2013&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We can look up the value for the year by finding the &amp;quot;Year:&amp;quot; label and then
selecting the next element, which will be the value:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Year:&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;Also see the
&lt;a href="https://news.ycombinator.com/item?id=25993258"&gt;Hacker News discussion&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</content><category term="Browser Extensions"></category><category term="web browsers"></category><category term="automation"></category><category term="scraping"></category></entry><entry><title>The PixieBrix manifesto: web customization for the masses</title><link href="https://toddschiller.com/blog/pixiebrix-manifesto.html" rel="alternate"></link><published>2020-09-02T00:00:00-04:00</published><updated>2020-09-02T00:00:00-04:00</updated><author><name>Todd Schiller</name></author><id>tag:toddschiller.com,2020-09-02:/blog/pixiebrix-manifesto.html</id><summary type="html">&lt;p&gt;My freshman year of college, I interned as an &amp;quot;equities analyst&amp;quot;. What this
meant in practice was that I was supposed to look up numbers in a terminal, and
then type them into an Excel spreadsheet. The software provider hadn't gotten
around to implementing export for the data yet.&lt;/p&gt;
&lt;p&gt;Not …&lt;/p&gt;</summary><content type="html">&lt;p&gt;My freshman year of college, I interned as an &amp;quot;equities analyst&amp;quot;. What this
meant in practice was that I was supposed to look up numbers in a terminal, and
then type them into an Excel spreadsheet. The software provider hadn't gotten
around to implementing export for the data yet.&lt;/p&gt;
&lt;p&gt;Not wanting to spend a whole summer entering data, I set out to put my computer
science classes to use. I coded up a flow where I took screenshots, passed them
through off-the-shelf OCR software, and then had a script add them to the
spreadsheet. The two-month project took one week, and I spent the rest of the
summer enjoying World Cup Soccer matches on the office TV.&lt;/p&gt;
&lt;p&gt;That's when I realized, given the right tools, even basic coding skills can
translate into massive productivity and quality of life gains.&lt;/p&gt;
&lt;p&gt;Now almost 15 years later, I see a mixed landscape. Improvements in development
tools and AI are creating new possibilities. However, non-devs are often worse
off. Perpetual licenses and open-source software have given way to subscriptions
and walled gardens. Software providers artificially limit the productivity of
their software by designing for the average user. Or worse, intentionally
restrict functionality.&lt;/p&gt;
&lt;p&gt;We started PixieBrix because we believe three things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;There is no such thing as an &amp;quot;average user&amp;quot;&lt;/li&gt;
&lt;li&gt;Everyone should be empowered to customize their software to give them
superpowers&lt;/li&gt;
&lt;li&gt;Computing should be delightful&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To this end, we've set out to create low/no-code tools for customizing the
behavior of software, web pages, and SaaS. But we won't be able to do it alone.
That's why we're also working hard to build a diverse and vibrant community of
makers.&lt;/p&gt;
&lt;p&gt;Our first PixieBrix product is a low/no-code platform for extending web
browsers. Web browsers are the new OS, and everyday people are constantly
switching between countless web pages and SaaS apps. Now, with a bit of
configuration, anyone using our platform can add functionality that previously
required custom browser extension development. This ranges from adding elements
to a page, to automating workflows à la robotic process automation (RPA).&lt;/p&gt;
&lt;p&gt;Currently, building new features in our system requires common coding skills
such as YAML, CSS selectors, JSON Schema, and regular expressions. Over time,
we'll be working hard to reduce the barriers to use, mix, match, and create.&lt;/p&gt;
&lt;p&gt;Fitting with our beliefs, I'm proud to say
our &lt;a href="https://github.com/pixiebrix/pixiebrix-extension"&gt;browser extension is open source (GPL v3)&lt;/a&gt;.
That means everyone can inspect what code they're running and modify it, as long
as they're willing to extend that benefit to others.&lt;/p&gt;
&lt;p&gt;I'd like to personally invite you to join us on our journey. To keep up to date,
subscribe to our newsletter. And, if you have an idea you'd like to build, send
me an email at &lt;a href="mailto:todd@pixiebrix.com"&gt;todd@pixiebrix.com&lt;/a&gt;. I'd love to hear
about it and help turn it into a reality.&lt;/p&gt;
</content><category term="Browser Extensions"></category><category term="web browsers"></category><category term="customization"></category><category term="browser extensions"></category></entry></feed>