Dynamic import() of extension modules from a content script makes Chrome log 'Unsafe attempt to load URL chrome-extension://...' on YouTube pages, which fills the extension's error list even though the modules load (HTTP 200). The content script is now one classic file built by 'npm run build'; web_accessible_resources is no longer needed and is removed. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
69 lines
2.9 KiB
JavaScript
69 lines
2.9 KiB
JavaScript
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { bundle } from '../tools/build-content.js';
|
|
|
|
const root = fileURLToPath(new URL('..', import.meta.url));
|
|
|
|
function project(files) {
|
|
const dir = mkdtempSync(join(tmpdir(), 'outlier-saver-bundle-'));
|
|
for (const [name, text] of Object.entries(files)) writeFileSync(join(dir, name), text);
|
|
return dir;
|
|
}
|
|
|
|
test('bundle puts dependencies before their users and leaves no import/export syntax', () => {
|
|
const dir = project({
|
|
'a.js': 'export const A = 1;\n',
|
|
'b.js': "import { A } from './a.js';\nexport function b() { return A + 1; }\n",
|
|
'main.js': "import { b } from './b.js';\nglobalThis.result = b();\n",
|
|
});
|
|
const output = bundle(join(dir, 'main.js'));
|
|
assert.ok(!/^\s*(import|export)\b/m.test(output), output);
|
|
const context = {};
|
|
new Function('globalThis', output)(context);
|
|
assert.equal(context.result, 2);
|
|
});
|
|
|
|
test('bundle includes a shared dependency only once', () => {
|
|
const dir = project({
|
|
'shared.js': 'export const SHARED = 7;\n',
|
|
'left.js': "import { SHARED } from './shared.js';\nexport const left = () => SHARED;\n",
|
|
'right.js': "import { SHARED } from './shared.js';\nexport const right = () => SHARED * 2;\n",
|
|
'main.js': "import { left } from './left.js';\nimport { right } from './right.js';\nglobalThis.sum = left() + right();\n",
|
|
});
|
|
const output = bundle(join(dir, 'main.js'));
|
|
assert.equal(output.match(/const SHARED/g).length, 1);
|
|
const context = {};
|
|
new Function('globalThis', output)(context);
|
|
assert.equal(context.sum, 21);
|
|
});
|
|
|
|
test('bundle refuses two modules that declare the same top-level name', () => {
|
|
const dir = project({
|
|
'a.js': 'export const X = 1;\n',
|
|
'b.js': 'export const X = 2;\n',
|
|
'main.js': "import { X } from './a.js';\nimport './b.js';\n",
|
|
});
|
|
assert.throws(() => bundle(join(dir, 'main.js')), /Duplicate top-level name "X"/);
|
|
});
|
|
|
|
test('bundle refuses export forms it cannot rewrite', () => {
|
|
for (const line of ['export default 1;', 'const a = 1;\nexport { a };', "export * from './x.js';"]) {
|
|
const dir = project({ 'main.js': `${line}\n` });
|
|
assert.throws(() => bundle(join(dir, 'main.js')), /only "export const/, line);
|
|
}
|
|
});
|
|
|
|
test('the committed src/content.js is up to date (run `npm run build` if this fails)', () => {
|
|
const built = bundle(join(root, 'src/content/main.js'));
|
|
assert.equal(readFileSync(join(root, 'src/content.js'), 'utf8'), built);
|
|
});
|
|
|
|
test('the content script has no dynamic import and the manifest has no web_accessible_resources', () => {
|
|
assert.ok(!/\bimport\s*\(/.test(readFileSync(join(root, 'src/content.js'), 'utf8')));
|
|
assert.equal('web_accessible_resources' in JSON.parse(readFileSync(join(root, 'manifest.json'), 'utf8')), false);
|
|
});
|