A cross-browser extension that allows you to highlight and manage text on web pages. Supports Chrome, Firefox, and Firefox for Android.
- Text Highlighting: Select and highlight text on web pages with multiple colors
- Highlight Management: Manage and review highlighted text per page
- Cross-Device Sync: Sync highlights across devices using browser
storage.sync - Minimap: View highlighted positions at a glance with a minimap on the right side of the page
- Multilingual Support: Available in English, Korean, Japanese, and Chinese
- Cross-Browser Support: Works on Chrome, Firefox, and Firefox for Android
- Keyboard Shortcuts: Quick highlighting with customizable keyboard shortcuts (desktop only)
- Selection Controls: Floating highlight UI on text selection, ideal for mobile devices
- Node.js 22.16.0 or higher
- npm 10.9.0 or higher
# Clone the repository
git clone https://github.com/yourusername/text-highlighter.git
cd text-highlighter
# Install dependencies
npm installThis extension supports:
- Chrome: Manifest V3 with native Chrome APIs
- Firefox: Manifest V3 with native WebExtensions APIs (requires Firefox 140.0+)
- Firefox for Android: Manifest V3 with mobile-optimized UI (requires Firefox for Android 142.0+)
On Firefox for Android, the following desktop-only APIs are unavailable and handled gracefully:
- Context Menus (
contextMenus): Not supported. Use the Selection Controls UI instead (enabled by default on mobile). - Keyboard Shortcuts (
commands): Not supported. Use the Selection Controls UI instead. - Windows API (
windows): Not supported. The extension uses the Tabs API as a fallback. - Sync Storage (
storage.sync): Sync currently works between Firefox desktop devices, but Firefox for Android does not sync highlight data.
The Selection Controls feature (floating highlight icon on text selection) is automatically enabled on mobile devices, providing a touch-friendly alternative to context menus and keyboard shortcuts.
Repository skills live in .agents/skills/, which is tracked in git and shared across agent tools. Claude Code only discovers skills under .claude/skills/, so link the two rather than keeping a second copy:
npm run link-skillsThis creates .claude/skills as a link to .agents/skills (a directory junction on Windows, which needs no elevation). Run it once per clone — .claude/ is gitignored, so the link is local to your machine. It is idempotent and safe to re-run. Restart Claude Code afterwards for the skills to be picked up.
Add new skills to .agents/skills/<name>/SKILL.md only; the link exposes them automatically.
Three suites, run separately:
# Unit and integration tests (Jest, jsdom)
npm test
# End-to-end tests on Chromium (Playwright)
npx playwright install # browsers, required before the first run
npx playwright test
# Firefox smoke tests (Selenium + geckodriver)
npm run test:e2e:firefoxnpm test and the Playwright suite run in CI on every push. The Firefox suite
does not: it is three checks that confirm the Firefox build comes up, applies a
highlight, restores it after a reload, and opens its popup — run it by hand
before publishing a Firefox release. It needs Firefox installed locally
(geckodriver is downloaded automatically), builds dist-firefox/ itself, and
takes a few seconds.
# Watch it run instead of going headless
HEADFUL=1 npm run test:e2e:firefox
# Point at a Firefox that is not in the default location
FIREFOX_BINARY="/path/to/firefox" npm run test:e2e:firefoxTo test the extension on a real Android device:
- Install Firefox for Android on your device
- Enable USB debugging on your Android device (Settings > Developer options > USB debugging)
- Connect your device via USB and authorize the connection
# Run on connected Android device
npx web-ext run -t firefox-android --adb-device <device-id> --firefox-apk org.mozilla.firefox -s dist-firefoxTo find your device ID:
adb devices- On your Android device, open Firefox and go to
about:config - Set
xpcom.debug.remote.enabledtotrue - On your desktop Firefox, go to
about:debugging> Setup - Add your device and connect
- Load the extension from
dist-firefox/manifest.json
# View extension logs from the Android device
adb logcat -s GeckoConsoleRun the deployment script to build both Chrome and Firefox extension files at once:
npm run deployTo build a single browser instead, use npm run deploy:chrome or npm run deploy:firefox.
Run the Chrome-specific deployment script to copy only the required files to the dist directory for loading into Chrome:
npm run deploy:chromeTo load the deployed extension in Chrome:
- Open
chrome://extensionsin Chrome browser - Enable "Developer mode" in the top right
- Click "Load unpacked extension"
- Select the generated
distdirectory
Run the Firefox-specific deployment script:
npm run deploy:firefoxTo load the deployed extension in Firefox:
- Open
about:debuggingin Firefox browser - Click "This Firefox" in the sidebar
- Click "Load Temporary Add-on"
- Select the
manifest.jsonfile from the generateddist-firefoxdirectory
For creating a production-ready extension package, you can now specify the target browser:
npm run version-deploy <version> [browser]This command will:
- Update the version in the appropriate manifest file (
manifest.jsonfor Chrome,manifest-firefox.jsonfor Firefox) - Set
DEBUG_MODEtofalsein all JavaScript files - Build the extension to the appropriate directory (
dist/for Chrome,dist-firefox/for Firefox) - Create a browser-specific zip file in the
outputs/directory
npm run version-deploy 1.2.0
# or explicitly
npm run version-deploy 1.2.0 chromeThis creates outputs/text-highlighter-1.2.0-chrome.zip ready for submission to the Chrome Web Store.
npm run version-deploy 1.2.0 firefoxThis creates outputs/text-highlighter-1.2.0-firefox.zip ready for submission to Firefox Add-ons (AMO).
Note: Each browser build uses its own manifest file and output directory, allowing you to maintain separate versions for each browser if needed.
The extension uses a browserAPI compatibility layer to support Chrome and Firefox:
- Chrome: Uses native
chrome.*APIs directly - Firefox (Desktop/Android): Uses native
browser.*APIs directly - Manifest Files: Separate manifests for browser-specific configurations
manifest.json: Chrome-optimized (default)manifest-firefox.json: Firefox-optimized withgeckoandgecko_androidsettings
| API | Chrome | Firefox Desktop | Firefox Android |
|---|---|---|---|
| Storage | O | O | O |
| Sync Storage | O | O | X |
| Tabs | O | O | O |
| Runtime | O | O | O |
| Internationalization | O | O | O |
| Context Menus | O | O | X |
| Commands | O | O | X |
| Windows | O | O | X |
On Firefox Android, unavailable APIs are conditionally guarded using browser.runtime.getPlatformInfo() to detect the platform at runtime.
Cloud sync (background/cloud-sync-service.js) runs a pull-merge-push cycle on a 15-minute alarm. The PUT is skipped when the merged blob matches the one just fetched, so an idle cycle costs a single GET.
That guard rests on an invariant worth knowing before touching this code: the merge must be a pure function of the two blobs, giving the same answer on every device. A rule phrased as "prefer mine" breaks it, because each device resolves it in its own favour — neither ever adopts the other, and every cycle pushes. Nothing errors; the symptom is a setting that quietly never syncs, plus a KV write on every cycle from users who changed nothing.
Two rules in mergeBlobs still resolve ties that way:
- settings: on equal
updatedAt, local wins (strict>) - page
title: local wins whenever it is non-empty, regardless of recency
Adoption has to be complete, too. applySettingsFromSync copies the sender's settings timestamp, so any field it declines to adopt leaves two devices claiming the same timestamp with different content — which is exactly the tie the rules above resolve badly. Treat null as a value, not as an absent field.
See #108 for a case that reached production, where a skipped null produced a PUT on roughly 20% of all sync cycles.
- Fork the project
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.