Files
IgorVolochayandClaude Sonnet 5.5 b137776381 fix: bundle the content script to stop 'Unsafe attempt to load URL' errors
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>
2026-10-25 12:25:06 +03:00

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);
});