chrome+firefox v0.3
This commit is contained in:
12
.gitignore
vendored
12
.gitignore
vendored
@@ -32,14 +32,10 @@ node_modules
|
|||||||
# Unpacked build output. Regenerated by "make", never edited by hand.
|
# Unpacked build output. Regenerated by "make", never edited by hand.
|
||||||
build
|
build
|
||||||
|
|
||||||
# Intermediate packages.
|
# dist/ holds the publishable packages and is NOT ignored: the signed add-on
|
||||||
logsdu-*.zip
|
# is what people download and install, so it belongs in the repository (or
|
||||||
|
# attached to a release) rather than being rebuilt by everyone who wants it.
|
||||||
# Packaged .xpi files are NOT ignored. They are the published artifact: the
|
# Local unsigned builds land there too; "make clean" removes the directory.
|
||||||
# signed add-on is what people download and install, so it belongs in the
|
|
||||||
# repository (or attached to a release) rather than being rebuilt by everyone
|
|
||||||
# who wants to install it. An unsigned local build can be removed with
|
|
||||||
# "make clean".
|
|
||||||
|
|
||||||
# Exclude sourcemaps
|
# Exclude sourcemaps
|
||||||
*.map
|
*.map
|
||||||
|
|||||||
74
Makefile
74
Makefile
@@ -19,33 +19,41 @@
|
|||||||
# * rcb@beco.cc *
|
# * rcb@beco.cc *
|
||||||
# **************************************************************************
|
# **************************************************************************
|
||||||
|
|
||||||
# Makefile for logsdu - build the unpacked extension into build/.
|
# Makefile for logsdu - build the extension for Firefox and Chrome.
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# make # typecheck and bundle into build/
|
# make # typecheck and bundle for Firefox into build/
|
||||||
|
# make chrome # same, but with Chrome's manifest, into build/
|
||||||
# make test # run the unit tests
|
# make test # run the unit tests
|
||||||
# make xpi # build, then package build/ as logsdu-<version>.xpi
|
# make smoke # check the background bundle works as a service worker
|
||||||
# make clean # remove build/ and the package
|
# make xpi # package the Firefox build -> dist/logsdu-<version>.xpi
|
||||||
|
# make crx # package the Chrome build -> dist/logsdu-<version>-chrome.zip
|
||||||
|
# make packages # both of the above
|
||||||
|
# make clean # remove build/ and dist/
|
||||||
|
#
|
||||||
|
# build/ holds the unpacked extension and is regenerated from scratch; dist/
|
||||||
|
# holds the packages meant to be published, and is kept out of build/ so that
|
||||||
|
# packaging never tries to include its own output.
|
||||||
#
|
#
|
||||||
# Dependencies are installed with pnpm, never npm:
|
# Dependencies are installed with pnpm, never npm:
|
||||||
# corepack pnpm install
|
# corepack pnpm install
|
||||||
#
|
#
|
||||||
# Permanent install (Firefox ESR, Developer Edition or Nightly):
|
# Load the unpacked build while developing:
|
||||||
# set xpinstall.signatures.required=false in about:config, then
|
# Firefox about:debugging -> This Firefox -> Load Temporary Add-on ->
|
||||||
# about:addons -> gear -> Install Add-on From File -> pick the .xpi
|
# build/manifest.json (dropped when Firefox restarts)
|
||||||
|
# Chrome chrome://extensions -> Developer mode -> Load unpacked -> build/
|
||||||
#
|
#
|
||||||
# Release Firefox refuses unsigned add-ons whatever that pref says. There the
|
# Publishing:
|
||||||
# same .xpi has to go through addons.mozilla.org as an unlisted add-on first,
|
# Firefox upload dist/*.xpi at addons.mozilla.org
|
||||||
# which signs it automatically without publishing or reviewing it.
|
# Chrome upload dist/*-chrome.zip at chrome.google.com/webstore/devconsole
|
||||||
#
|
|
||||||
# Throwaway install for development: about:debugging -> This Firefox ->
|
|
||||||
# Load Temporary Add-on -> pick build/manifest.json (dropped on restart).
|
|
||||||
|
|
||||||
EXT_ID := logsdu
|
EXT_ID := logsdu
|
||||||
VERSION := $(shell node -p "require('./package.json').version")
|
VERSION := $(shell node -p "require('./package.json').version")
|
||||||
XPI := $(EXT_ID)-$(VERSION).xpi
|
DIST := dist
|
||||||
|
XPI := $(DIST)/$(EXT_ID)-$(VERSION).xpi
|
||||||
|
CRX := $(DIST)/$(EXT_ID)-$(VERSION)-chrome.zip
|
||||||
|
|
||||||
.PHONY: all build chrome test xpi clean check-deps
|
.PHONY: all build chrome test smoke xpi crx packages clean check-deps
|
||||||
|
|
||||||
all: build
|
all: build
|
||||||
|
|
||||||
@@ -55,23 +63,29 @@ all: build
|
|||||||
test:
|
test:
|
||||||
node --test "src/**/*.test.ts"
|
node --test "src/**/*.test.ts"
|
||||||
|
|
||||||
|
# Runs the built background bundle in a service-worker-shaped sandbox, which
|
||||||
|
# is where a Chrome-only breakage would otherwise hide until runtime.
|
||||||
|
smoke: build
|
||||||
|
node tools/sw-smoke.mjs
|
||||||
|
|
||||||
# Call the local toolchain directly, so this works regardless of how pnpm is
|
# Call the local toolchain directly, so this works regardless of how pnpm is
|
||||||
# provided (corepack vs standalone). Run "corepack pnpm install" first.
|
# provided (corepack vs standalone). Run "corepack pnpm install" first.
|
||||||
build: check-deps
|
build: check-deps
|
||||||
node_modules/.bin/tsc -noEmit -skipLibCheck
|
node_modules/.bin/tsc -noEmit -skipLibCheck
|
||||||
node esbuild.config.mjs production
|
node esbuild.config.mjs production
|
||||||
|
|
||||||
# The same sources with Chrome's background key. Untested against Chrome; it
|
# The same sources with Chrome's manifest. Firefox and Chrome disagree on the
|
||||||
# exists so the port is a build flag rather than a fork.
|
# background key and on the gecko block, so the manifest is generated per
|
||||||
|
# target rather than forked.
|
||||||
chrome: check-deps
|
chrome: check-deps
|
||||||
node_modules/.bin/tsc -noEmit -skipLibCheck
|
node_modules/.bin/tsc -noEmit -skipLibCheck
|
||||||
TARGET=chrome node esbuild.config.mjs production
|
TARGET=chrome node esbuild.config.mjs production
|
||||||
@echo "Chrome build in build/ -- load it via chrome://extensions (Developer mode)."
|
@echo "Chrome build in build/ -- chrome://extensions -> Developer mode -> Load unpacked."
|
||||||
|
|
||||||
# An .xpi is just a zip of the extension directory, with the manifest at the
|
# An .xpi is just a zip of the extension directory, with the manifest at the
|
||||||
# top level rather than inside a wrapper folder. The same file installs
|
# top level rather than inside a wrapper folder.
|
||||||
# directly on ESR and uploads to AMO for signing.
|
|
||||||
xpi: build
|
xpi: build
|
||||||
|
@mkdir -p $(DIST)
|
||||||
rm -f $(XPI)
|
rm -f $(XPI)
|
||||||
cd build && zip -qr ../$(XPI) .
|
cd build && zip -qr ../$(XPI) .
|
||||||
@echo
|
@echo
|
||||||
@@ -80,7 +94,8 @@ xpi: build
|
|||||||
@echo "This file is UNSIGNED. Two ways to use it:"
|
@echo "This file is UNSIGNED. Two ways to use it:"
|
||||||
@echo
|
@echo
|
||||||
@echo " Publish -- upload it at addons.mozilla.org/developers/addon/submit/"
|
@echo " Publish -- upload it at addons.mozilla.org/developers/addon/submit/"
|
||||||
@echo " Mozilla signs it; the signed file installs on any Firefox."
|
@echo " Listed add-ons are signed once review approves them; unlisted"
|
||||||
|
@echo " ones are signed straight away."
|
||||||
@echo
|
@echo
|
||||||
@echo " Install locally -- only on ESR, Developer Edition or Nightly:"
|
@echo " Install locally -- only on ESR, Developer Edition or Nightly:"
|
||||||
@echo " 1. about:config -> xpinstall.signatures.required = false"
|
@echo " 1. about:config -> xpinstall.signatures.required = false"
|
||||||
@@ -89,9 +104,22 @@ xpi: build
|
|||||||
@echo " $(CURDIR)/$(XPI)"
|
@echo " $(CURDIR)/$(XPI)"
|
||||||
@echo
|
@echo
|
||||||
|
|
||||||
|
# The Chrome Web Store takes a plain zip, and does the packing into .crx itself.
|
||||||
|
crx: chrome
|
||||||
|
@mkdir -p $(DIST)
|
||||||
|
rm -f $(CRX)
|
||||||
|
cd build && zip -qr ../$(CRX) .
|
||||||
|
@echo
|
||||||
|
@echo "Built: $(CURDIR)/$(CRX)"
|
||||||
|
@echo "Upload it at chrome.google.com/webstore/devconsole"
|
||||||
|
@echo
|
||||||
|
|
||||||
|
# Both packages. The Firefox build runs last so build/ is left in the state
|
||||||
|
# the development instructions assume.
|
||||||
|
packages: crx xpi
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf build
|
rm -rf build $(DIST)
|
||||||
rm -f $(EXT_ID)-*.xpi $(EXT_ID)-*.zip
|
|
||||||
|
|
||||||
# Fail with a useful message rather than a confusing "tsc: not found".
|
# Fail with a useful message rather than a confusing "tsc: not found".
|
||||||
check-deps:
|
check-deps:
|
||||||
|
|||||||
46
README.md
46
README.md
@@ -19,7 +19,7 @@ Once it is published, from addons.mozilla.org. Until then, build it yourself:
|
|||||||
|
|
||||||
```
|
```
|
||||||
corepack pnpm install # first time, or after a dependency change
|
corepack pnpm install # first time, or after a dependency change
|
||||||
make xpi # typecheck, bundle, and package logsdu-<version>.xpi
|
make xpi # typecheck, bundle, and package dist/logsdu-<version>.xpi
|
||||||
```
|
```
|
||||||
|
|
||||||
`make xpi` prints the full path of the file and how to install it. The package
|
`make xpi` prints the full path of the file and how to install it. The package
|
||||||
@@ -118,23 +118,56 @@ check.
|
|||||||
|
|
||||||
## Chrome
|
## Chrome
|
||||||
|
|
||||||
`make chrome` builds it. The only difference is the background key: Firefox MV3
|
```
|
||||||
uses an event page, Chrome MV3 requires a service worker, so the manifest is
|
make chrome # unpacked Chrome build in build/
|
||||||
generated per target rather than duplicated. Everything else -- `chrome.*`
|
make crx # package it as dist/logsdu-<version>-chrome.zip
|
||||||
namespace, MV3, no Firefox-only APIs -- is already shared.
|
```
|
||||||
|
|
||||||
It has not been tested against Chrome. Do not assume it works.
|
Load `build/` via `chrome://extensions` -> Developer mode -> **Load unpacked**,
|
||||||
|
or upload the zip at
|
||||||
|
[the Web Store dashboard](https://chrome.google.com/webstore/devconsole).
|
||||||
|
|
||||||
|
One codebase, two manifests. Chrome MV3 requires a background *service worker*
|
||||||
|
and rejects Firefox's event-page `background.scripts`; Firefox needs the gecko
|
||||||
|
block that Chrome has no use for. `esbuild.config.mjs` writes the right manifest
|
||||||
|
per target, so the port is a build flag rather than a fork. Everything else --
|
||||||
|
the `chrome.*` namespace, MV3, the permission model -- is shared.
|
||||||
|
|
||||||
|
Two things that differ in practice, both handled:
|
||||||
|
|
||||||
|
- **Icons must be raster.** Chrome does not accept SVG in `icons`, so the PNGs
|
||||||
|
in `icons/` are generated from `logsdu.svg` and both browsers use those.
|
||||||
|
- **Service workers have no `window` or `document`.** A stray reference through
|
||||||
|
a shared import would break Chrome only, silently, at runtime. `make smoke`
|
||||||
|
runs the built background bundle in a worker-shaped sandbox to catch that.
|
||||||
|
|
||||||
|
What has been verified: Chrome 151 loads the build without errors, and the
|
||||||
|
background bundle registers exactly one content script for exactly the
|
||||||
|
configured origin. What has **not** been verified is a real login against a live
|
||||||
|
portal in Chrome.
|
||||||
|
|
||||||
|
A caveat that applies to both browsers: the page-world filler is injected as a
|
||||||
|
`<script src>` tag, which a site's Content-Security-Policy can refuse. Portals
|
||||||
|
that send no CSP -- the common case for this kind of form -- are unaffected. A
|
||||||
|
portal that does would need the filler registered as a `MAIN` world content
|
||||||
|
script instead.
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
```
|
```
|
||||||
corepack pnpm install
|
corepack pnpm install
|
||||||
make test # unit tests
|
make test # unit tests
|
||||||
|
make smoke # background bundle under a service worker
|
||||||
make # typecheck and bundle into build/
|
make # typecheck and bundle into build/
|
||||||
|
make packages # both publishable packages into dist/
|
||||||
corepack pnpm run dev # rebuild on change
|
corepack pnpm run dev # rebuild on change
|
||||||
make clean
|
make clean
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`build/` is the unpacked extension and is regenerated from scratch every time.
|
||||||
|
`dist/` holds the packages meant to be published, and is kept separate so that
|
||||||
|
packaging never tries to include its own output.
|
||||||
|
|
||||||
While iterating, load the unpacked directory rather than reinstalling an `.xpi`
|
While iterating, load the unpacked directory rather than reinstalling an `.xpi`
|
||||||
each time: `about:debugging` -> **This Firefox** -> **Load Temporary Add-on** ->
|
each time: `about:debugging` -> **This Firefox** -> **Load Temporary Add-on** ->
|
||||||
`build/manifest.json`, then press **Reload** there after each rebuild. That copy
|
`build/manifest.json`, then press **Reload** there after each rebuild. That copy
|
||||||
@@ -156,6 +189,7 @@ still needs the Reload click to pick anything up.
|
|||||||
| `src/config.ts` | Stored values, the rate limit and the logout cooldown |
|
| `src/config.ts` | Stored values, the rate limit and the logout cooldown |
|
||||||
| `src/format.ts` | Input normalisers for the three masked fields |
|
| `src/format.ts` | Input normalisers for the three masked fields |
|
||||||
| `src/options.*`, `src/popup.*` | The two bits of UI |
|
| `src/options.*`, `src/popup.*` | The two bits of UI |
|
||||||
|
| `tools/sw-smoke.mjs` | Checks the background bundle survives a service worker |
|
||||||
|
|
||||||
### Why two scripts instead of one
|
### Why two scripts instead of one
|
||||||
|
|
||||||
|
|||||||
BIN
logsdu-0.2.0.xpi → dist/logsdu-0.3.0-chrome.zip
vendored
BIN
logsdu-0.2.0.xpi → dist/logsdu-0.3.0-chrome.zip
vendored
Binary file not shown.
BIN
dist/logsdu-0.3.0.xpi
vendored
Normal file
BIN
dist/logsdu-0.3.0.xpi
vendored
Normal file
Binary file not shown.
BIN
icons/logsdu-128.png
Normal file
BIN
icons/logsdu-128.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
BIN
icons/logsdu-16.png
Normal file
BIN
icons/logsdu-16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 442 B |
BIN
icons/logsdu-32.png
Normal file
BIN
icons/logsdu-32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 729 B |
BIN
icons/logsdu-48.png
Normal file
BIN
icons/logsdu-48.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
icons/logsdu-96.png
Normal file
BIN
icons/logsdu-96.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "logsdu",
|
"name": "logsdu",
|
||||||
"version": "0.2.0",
|
"version": "0.3.0",
|
||||||
"description": "Browser extension that fills and submits a three-field academic portal login.",
|
"description": "Browser extension that fills and submits a three-field academic portal login.",
|
||||||
"author": "Ruben Carlo Benante <rcb@beco.cc>",
|
"author": "Ruben Carlo Benante <rcb@beco.cc>",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "logsdu",
|
"name": "logsdu",
|
||||||
"version": "0.2.0",
|
"version": "0.3.0",
|
||||||
"description": "Saves and fills logins that password managers cannot: registration number, date of birth and document number.",
|
"description": "Saves and fills logins that password managers cannot: registration number, date of birth and document number.",
|
||||||
"author": "Ruben Carlo Benante (Dr. Beco)",
|
"author": "Ruben Carlo Benante (Dr. Beco)",
|
||||||
"homepage_url": "https://code.beco.cc/beco/logsdu",
|
"homepage_url": "https://code.beco.cc/beco/logsdu",
|
||||||
"icons": {
|
"icons": {
|
||||||
"48": "icons/logsdu.svg",
|
"16": "icons/logsdu-16.png",
|
||||||
"96": "icons/logsdu.svg"
|
"32": "icons/logsdu-32.png",
|
||||||
|
"48": "icons/logsdu-48.png",
|
||||||
|
"96": "icons/logsdu-96.png",
|
||||||
|
"128": "icons/logsdu-128.png"
|
||||||
},
|
},
|
||||||
"browser_specific_settings": {
|
"browser_specific_settings": {
|
||||||
"gecko": {
|
"gecko": {
|
||||||
@@ -36,6 +39,9 @@
|
|||||||
"action": {
|
"action": {
|
||||||
"default_popup": "popup.html",
|
"default_popup": "popup.html",
|
||||||
"default_title": "logsdu",
|
"default_title": "logsdu",
|
||||||
"default_icon": "icons/logsdu.svg"
|
"default_icon": {
|
||||||
|
"16": "icons/logsdu-16.png",
|
||||||
|
"32": "icons/logsdu-32.png"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
135
tools/sw-smoke.mjs
Normal file
135
tools/sw-smoke.mjs
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
// *************************************************************************
|
||||||
|
// * (C)opyright 2026 by Ruben Carlo Benante *
|
||||||
|
// * *
|
||||||
|
// * This program is free software; you can redistribute it and/or modify *
|
||||||
|
// * it under the terms of the GNU General Public License as published by *
|
||||||
|
// * the Free Software Foundation, either version 3 of the License, or *
|
||||||
|
// * (at your option) any later version. *
|
||||||
|
// * *
|
||||||
|
// * This program is distributed in the hope that it will be useful, *
|
||||||
|
// * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||||
|
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||||
|
// * GNU General Public License for more details. *
|
||||||
|
// * *
|
||||||
|
// * You should have received a copy of the GNU General Public License *
|
||||||
|
// * along with this program. If not, see http://www.gnu.org/licenses/. *
|
||||||
|
// * *
|
||||||
|
// * Contact author at: *
|
||||||
|
// * Ruben Carlo Benante *
|
||||||
|
// * rcb@beco.cc *
|
||||||
|
// *************************************************************************
|
||||||
|
|
||||||
|
// Smoke test for the background bundle under Chrome's execution model.
|
||||||
|
//
|
||||||
|
// Chrome MV3 runs the background as a service worker, where there is no
|
||||||
|
// `window` and no `document`, and the global object is `self`. Firefox runs
|
||||||
|
// the same file as an event page, where those do exist -- so a reference that
|
||||||
|
// creeps in through a shared import breaks Chrome only, and breaks it silently
|
||||||
|
// at runtime rather than at build time.
|
||||||
|
//
|
||||||
|
// This evaluates the built bundle in a worker-shaped sandbox with a stubbed
|
||||||
|
// extension API, and asserts that it registers exactly one content script, for
|
||||||
|
// exactly the configured origin. Run it against build/ after a build:
|
||||||
|
//
|
||||||
|
// make smoke
|
||||||
|
|
||||||
|
import { readFileSync } from 'node:fs';
|
||||||
|
import vm from 'node:vm';
|
||||||
|
|
||||||
|
const BUNDLE = 'build/background.js';
|
||||||
|
const CONFIGURED_ORIGIN = 'https://portal.example.br';
|
||||||
|
|
||||||
|
const permissionChecks = [];
|
||||||
|
const registered = [];
|
||||||
|
const listeners = {
|
||||||
|
onInstalled: 0,
|
||||||
|
onStartup: 0,
|
||||||
|
onRemoved: 0,
|
||||||
|
onChanged: 0,
|
||||||
|
onMessage: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const chrome = {
|
||||||
|
runtime: {
|
||||||
|
onInstalled: { addListener: () => listeners.onInstalled++ },
|
||||||
|
onStartup: { addListener: () => listeners.onStartup++ },
|
||||||
|
onMessage: { addListener: () => listeners.onMessage++ },
|
||||||
|
},
|
||||||
|
permissions: {
|
||||||
|
onRemoved: { addListener: () => listeners.onRemoved++ },
|
||||||
|
contains: async (p) => {
|
||||||
|
permissionChecks.push(p.origins);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
storage: {
|
||||||
|
local: {
|
||||||
|
get: async () => ({
|
||||||
|
config: {
|
||||||
|
url: `${CONFIGURED_ORIGIN}/`,
|
||||||
|
ra: '2000101010',
|
||||||
|
dn: '01/02/1999',
|
||||||
|
cpf: '123.456.789-01',
|
||||||
|
autoSubmit: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
set: async () => {},
|
||||||
|
},
|
||||||
|
onChanged: { addListener: () => listeners.onChanged++ },
|
||||||
|
},
|
||||||
|
scripting: {
|
||||||
|
getRegisteredContentScripts: async () => [],
|
||||||
|
unregisterContentScripts: async () => {},
|
||||||
|
registerContentScripts: async (scripts) => registered.push(...scripts),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Deliberately no window and no document: referencing either must fail here
|
||||||
|
// exactly as it would inside a service worker.
|
||||||
|
const sandbox = { chrome, console, setTimeout, clearTimeout, queueMicrotask, URL };
|
||||||
|
sandbox.self = sandbox;
|
||||||
|
vm.createContext(sandbox);
|
||||||
|
|
||||||
|
const failures = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
vm.runInContext(readFileSync(BUNDLE, 'utf8'), sandbox, { filename: BUNDLE });
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`FAIL: ${BUNDLE} threw on evaluation: ${error.message}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Give the top-level resync() a turn of the event loop to settle.
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||||
|
|
||||||
|
for (const [name, count] of Object.entries(listeners)) {
|
||||||
|
if (count !== 1) failures.push(`${name} registered ${count} times, expected 1`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (registered.length !== 1) {
|
||||||
|
failures.push(`registered ${registered.length} content scripts, expected 1`);
|
||||||
|
} else {
|
||||||
|
const script = registered[0];
|
||||||
|
const expected = `${CONFIGURED_ORIGIN}/*`;
|
||||||
|
if (script.matches?.length !== 1 || script.matches[0] !== expected) {
|
||||||
|
failures.push(`matches ${JSON.stringify(script.matches)}, expected ["${expected}"]`);
|
||||||
|
}
|
||||||
|
if (script.js?.[0] !== 'content.js') {
|
||||||
|
failures.push(`js ${JSON.stringify(script.js)}, expected ["content.js"]`);
|
||||||
|
}
|
||||||
|
if (script.persistAcrossSessions !== false) {
|
||||||
|
failures.push('persistAcrossSessions must be false, or startup registers twice');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (permissionChecks.length !== 1) {
|
||||||
|
failures.push(`checked permissions ${permissionChecks.length} times, expected 1`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failures.length > 0) {
|
||||||
|
for (const failure of failures) console.error(`FAIL: ${failure}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('ok - background bundle runs as a service worker');
|
||||||
|
console.log(`ok - registers content.js for ${CONFIGURED_ORIGIN}/* and nothing else`);
|
||||||
Reference in New Issue
Block a user