6.6 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
What this is
GreasyBoii is a browser extension (a lightweight Greasemonkey/Tampermonkey-style tool) that injects custom JavaScript and CSS into pages based on URL-substring or CSS-selector match rules, configured through a popup UI. There is no build system, package manager, or test suite — it's plain HTML/CSS/JS shipped straight to the browser.
Repository layout
The extension is maintained as two parallel, independently-edited implementations:
chrome/— Manifest V3 (service worker background script)firefox/— Manifest V2 (persistent background script,browser.*namespace)
Each directory is a complete, self-contained extension (manifest, background script, content
script, popup HTML/CSS/JS, vendored Bootstrap, icons). There is no shared source — a
change to matching/rule logic in one directory must be manually ported to the other. When
editing behavior, always check whether the same fix is needed in both chrome/*.js and
firefox/*.js; diff the two files first (diff chrome/foo.js firefox/foo.js) to see how they
currently diverge before deciding how to apply a change to both.
Key API differences between the two versions:
- Namespace:
chrome.*vs. the promise-basedbrowser.*. Style is otherwise mixed on the Chrome side rather than uniform:chrome/background.jskeeps the original callback style throughout;chrome/content.jsandchrome/popup.jswere ported from the Firefox files and use the promise form instead (valid since Chrome 88+ returns a Promise from mostchrome.*calls when the callback is omitted — the manifest already requires Chrome 88+). Don't "fix" this into one style without checking both still work; it's a leftover of how the port was done, not a bug. - Chrome's
background.jshandles JS execution viachrome.scripting.executeScript(world:MAIN) to bypass page CSP, relayed there fromchrome/content.js'sinjectJavaScript; Firefox'scontent.jsinstead injects a<script>tag directly (with awrappedJSObject.evalattempt first) since Firefox's MV2 background script can't reach the page context the same way. This is the one piece of rule-execution logic that's genuinely different between the two, not just a namespace swap. - Both
background.jsfiles now do more than proxygetRules: they also relay the element picker's result (pickerResult/pickerCancelled→storage.local, since the popup closes before the page click lands) and handle thetoggle-greasyboiikeyboard command.
Both browsers reached v2.0.0 feature parity together (see CHANGELOG.md 2.0.0): advanced
rule actions (auto-click, text-replace, MutationObserver-based SPA re-apply — see
executeRule/watchForChanges in content.js), a visual element picker
(startElementPicker/computeSelector in content.js; the relay/restore dance lives in
restorePendingPicker/collectModalSnapshot in popup.js), on-demand page tools (bulk
media download, link harvesting, data scraping — the runtime.onMessage handlers under
"On-demand tools" in content.js), rule import/export, a one-click dark-mode rule generator,
and the master kill-switch. It shipped Firefox-first; chrome/popup.js is close to a
mechanical browser. → chrome. port of firefox/popup.js (see the API-differences bullets
above for what didn't port mechanically).
Core architecture (both browsers)
content.jsruns on every page (document_idle, all frames excluded). It readsrulesfromstorage.sync, evaluates each enabled rule's match condition against the current page, and for matches injects the rule's JS and/or CSS.- Rule matching (
matchType):url(substring match againstlocation.href),element(adocument.querySelectorhit), orboth(both must hold). See theinitGreasyBoiifunction incontent.jsfor the exact matching logic. background.jsbrokers privileged operations content scripts can't do directly (reading storage, and on Chrome, executing page-world JS to bypass CSP).popup.jsis the rule editor CRUD UI (Bootstrap 5 dark theme, vendored locally after a CSP issue with the CDN version — see CHANGELOG.md 1.0.2). It reads/writes the samerulesarray instorage.sync, keyed by array index (not by id), and re-renders the list viarenderRules()/attachRuleEventListeners()(event delegation) after every mutation.
Storage format
A single storage.sync key rules holding an array of rule objects:
{ name, matchType, urlPattern, elementSelector, javascript, css, enabled }
Rule objects (v2.0.0+, both browsers) also carry optional advanced fields:
autoClickSelector, autoClickIntervalSec, autoClickMax, reapplyOnDomChanges,
textReplacements (an array of {find, replace}). There's also a separate top-level
storage.sync key, masterEnabled (boolean, default true) — the global kill-switch, read
by content.js before evaluating any rule and toggled by background.js/the popup header
switch.
There is no schema migration mechanism — if you change this shape, both content.js files and
popup.js (both copies) need to keep reading/writing it consistently, and existing stored
rules from older versions won't be retrofitted.
Manual testing (no automated tests exist)
- Load unpacked: Chrome →
chrome://extensions/→ enable Developer mode → "Load unpacked" → selectchrome/. Firefox →about:debugging#/runtime/this-firefox→ "Load Temporary Add-on" → selectfirefox/manifest.json. - Reload after edits: Chrome extensions page reload icon; Firefox
about:debugging→ Reload. test-page.htmlin the repo root is a scratch page for exercising rules against.- Debug surfaces: content script logs to the page console (F12); background script logs to
the extension's service worker console (Chrome) or the
about:debuggingInspect view (Firefox); popup logs via right-click → Inspect on the open popup.
Packaging
./package-all.sh just calls ./package-chrome-release.sh and ./package-firefox-release.sh.
Each reads its own version out of its own manifest.json, validates the required-files
checklist from PACKAGING.md, and zips its folder's contents (not the folder itself) to
dist/greasyboii-<browser>-v<version>.zip with manifest.json at the zip root. Neither
script hardcodes a version — bump the relevant manifest.json and the next run picks it up.
See PACKAGING.md for store submission steps (Chrome Web Store, addons.mozilla.org) and
BUILD.md for the underlying per-browser packing mechanics.