chrome+firefox v0.3

This commit is contained in:
2026-08-10 15:49:01 -03:00
parent 17ad302a6c
commit a7531914f9
14 changed files with 242 additions and 43 deletions

135
tools/sw-smoke.mjs Normal file
View 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`);