Skip to content

Commit eeed585

Browse files
committed
Release v3.5.0 - @weight injection priority directive (Phase 11.7)
The Userscripts (Safari) @weight 1..999 directive is now parsed and used as an injection priority hint - higher values inject earlier within the same @run-at. Out-of-range values are clamped to the documented range so an @weight 99999 typo can't dominate the sort. registerAllScripts now sorts by Math.max(meta.priority, meta.weight) so authors who set both directives don't get surprised by the lower one winning. Existing @priority behavior preserved. GM_info.script.weight and GM_info.script.priority are exposed so scripts can introspect their own ordering hints. 5 parser tests cover valid range, clamp-above, clamp-below, default 0, and non-numeric values. 585 tests total. TS mirrors in src/types/script.ts and src/background/parser.ts updated to match. The existing JS-test parser duplicate (which mirrors the production parser inline) also got the new field.
1 parent 2f6b267 commit eeed585

10 files changed

Lines changed: 112 additions & 13 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22

33
All notable changes to ScriptVault will be documented in this file.
44

5+
## [v3.5.0]`@weight` injection priority (Phase 11.7)
6+
7+
- Added: `// @weight 1..999` directive (Userscripts/Safari standard). Higher = earlier within the same `@run-at`. Clamped to the documented range so an `@weight 99999` typo can't dominate the sort.
8+
- Changed: `registerAllScripts` sort now uses `Math.max(meta.priority || 0, meta.weight || 0)` so authors who set both don't get surprised by the lower one winning. Existing `@priority` behavior preserved.
9+
- Added: `GM_info.script.weight` and `GM_info.script.priority` so scripts can introspect their own injection ordering hints.
10+
- Added: 5 parser tests covering valid range, clamp-above, clamp-below, default, non-numeric. 585 tests pass.
11+
- Internal: TS mirrors in `src/types/script.ts` and `src/background/parser.ts` now declare the new field; the existing JS-test parser duplicate matched.
12+
513
## [v3.4.0] — Run on This Tab via chrome.userScripts.execute() (Phase 11.4)
614

715
- Added: "Run on This Tab" entry in the popup script-action dropdown. Fires the script once on the active tab without registering it for future page loads — useful for quick-test workflows and for running scripts that aren't enabled or that don't match the current URL.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
</p>
1010

1111
<p align="center">
12-
<img src="https://img.shields.io/badge/version-3.4.0-22c55e?style=flat-square" alt="Version">
12+
<img src="https://img.shields.io/badge/version-3.5.0-22c55e?style=flat-square" alt="Version">
1313
<img src="https://img.shields.io/badge/manifest-v3-60a5fa?style=flat-square" alt="Manifest V3">
1414
<img src="https://img.shields.io/badge/license-MIT-orange?style=flat-square" alt="License">
1515
<img src="https://img.shields.io/badge/chrome-120%2B-blue?style=flat-square" alt="Chrome 120+">

ROADMAP.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -651,6 +651,11 @@ Several metadata directives parsed by Violentmonkey and Tampermonkey are not yet
651651

652652
Priority order: `@inject-into` and `@connect` are HIGH (security-relevant and broadly compatible); the rest are MEDIUM parity items.
653653

654+
**Status (rolling):**
655+
- `@inject-into`, `@connect`, `@tag`, `@antifeature`, `@compatible`, `@incompatible`, `@top-level-await`all parsed in `background.core.js`. `@antifeature` warning banner already lives in the install dialog.
656+
- `@run-at document-body`recognized by the parser; currently maps to `document_end` for `chrome.userScripts.register()` since Chrome lacks a native body-only injection point. A MutationObserver shim that fires when `<body>` appears is still the right behavior; deferred.
657+
- **`@weight 1..999` shipped in v3.5.0 (2026-05-02).** Parser clamps to documented range; `registerAllScripts` sort uses `Math.max(priority, weight)`; `GM_info.script.weight` + `GM_info.script.priority` exposed. TS mirrors in `src/types/script.ts` + `src/background/parser.ts` matched. 5 parser tests cover valid/clamp-above/clamp-below/default/non-numeric.
658+
654659
### 11.8 `@require` Subresource Integrity
655660

656661
Tampermonkey supports SRI hashes appended to `@require` URLs:

background.core.js

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ function parseUserscript(code) {
128128
incompatible: [],
129129
webRequest: null,
130130
priority: 0,
131+
weight: 0,
131132
crontab: ''
132133
};
133134

@@ -206,6 +207,15 @@ function parseUserscript(code) {
206207
case 'priority':
207208
meta.priority = parseInt(value, 10) || 0;
208209
break;
210+
case 'weight': {
211+
// Phase 11.7 — Userscripts (Safari) `@weight 1..999`. Integer
212+
// injection priority where higher = earlier within the same
213+
// `@run-at`. Clamp to the documented range so an `@weight 99999`
214+
// typo can't dominate the sort.
215+
const w = parseInt(value, 10);
216+
if (Number.isFinite(w)) meta.weight = Math.max(1, Math.min(999, w));
217+
break;
218+
}
209219
case 'webRequest':
210220
try { meta.webRequest = JSON.parse(value); } catch (e) {}
211221
break;
@@ -4913,10 +4923,14 @@ async function registerAllScripts(forceReregister = false) {
49134923

49144924
const enabledScripts = scripts.filter(s => s.enabled !== false);
49154925

4916-
// Sort by @priority (higher = first), then position
4926+
// Sort by combined @priority + @weight (higher = first), then position.
4927+
// @priority is the legacy ScriptVault directive; @weight is the Userscripts
4928+
// (Safari) standard (1..999). Either bumps a script earlier within the same
4929+
// @run-at — we take the max so authors who set both don't get surprised by
4930+
// the lower one winning.
49174931
enabledScripts.sort((a, b) => {
4918-
const pa = a.meta?.priority || 0;
4919-
const pb = b.meta?.priority || 0;
4932+
const pa = Math.max(a.meta?.priority || 0, a.meta?.weight || 0);
4933+
const pb = Math.max(b.meta?.priority || 0, b.meta?.weight || 0);
49204934
if (pb !== pa) return pb - pa;
49214935
return (a.position || 0) - (b.position || 0);
49224936
});
@@ -5850,7 +5864,10 @@ ${req.code}
58505864
license: meta.license || '',
58515865
updateURL: meta.updateURL || '',
58525866
downloadURL: meta.downloadURL || '',
5853-
supportURL: meta.supportURL || ''
5867+
supportURL: meta.supportURL || '',
5868+
// Phase 11.7 — Userscripts (Safari) injection priority.
5869+
weight: meta.weight || 0,
5870+
priority: meta.priority || 0
58545871
},
58555872
scriptMetaStr: ${JSON.stringify(script.code.match(/\/\/\s*==UserScript==([\s\S]*?)\/\/\s*==\/UserScript==/)?.[0] || '')},
58565873
scriptHandler: 'ScriptVault',

background.js

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// ScriptVault v3.4.0 - Background Service Worker
1+
// ScriptVault v3.5.0 - Background Service Worker
22
// Comprehensive userscript manager with cloud sync and auto-updates
33
// NOTE: This file is built from source modules. Edit the individual files in
44
// shared/, modules/, and lib/, then run `npm run build` to regenerate.
@@ -10746,6 +10746,7 @@ function parseUserscript(code) {
1074610746
incompatible: [],
1074710747
webRequest: null,
1074810748
priority: 0,
10749+
weight: 0,
1074910750
crontab: ''
1075010751
};
1075110752

@@ -10824,6 +10825,15 @@ function parseUserscript(code) {
1082410825
case 'priority':
1082510826
meta.priority = parseInt(value, 10) || 0;
1082610827
break;
10828+
case 'weight': {
10829+
// Phase 11.7 — Userscripts (Safari) `@weight 1..999`. Integer
10830+
// injection priority where higher = earlier within the same
10831+
// `@run-at`. Clamp to the documented range so an `@weight 99999`
10832+
// typo can't dominate the sort.
10833+
const w = parseInt(value, 10);
10834+
if (Number.isFinite(w)) meta.weight = Math.max(1, Math.min(999, w));
10835+
break;
10836+
}
1082710837
case 'webRequest':
1082810838
try { meta.webRequest = JSON.parse(value); } catch (e) {}
1082910839
break;
@@ -15531,10 +15541,14 @@ async function registerAllScripts(forceReregister = false) {
1553115541

1553215542
const enabledScripts = scripts.filter(s => s.enabled !== false);
1553315543

15534-
// Sort by @priority (higher = first), then position
15544+
// Sort by combined @priority + @weight (higher = first), then position.
15545+
// @priority is the legacy ScriptVault directive; @weight is the Userscripts
15546+
// (Safari) standard (1..999). Either bumps a script earlier within the same
15547+
// @run-at — we take the max so authors who set both don't get surprised by
15548+
// the lower one winning.
1553515549
enabledScripts.sort((a, b) => {
15536-
const pa = a.meta?.priority || 0;
15537-
const pb = b.meta?.priority || 0;
15550+
const pa = Math.max(a.meta?.priority || 0, a.meta?.weight || 0);
15551+
const pb = Math.max(b.meta?.priority || 0, b.meta?.weight || 0);
1553815552
if (pb !== pa) return pb - pa;
1553915553
return (a.position || 0) - (b.position || 0);
1554015554
});
@@ -16468,7 +16482,10 @@ ${req.code}
1646816482
license: meta.license || '',
1646916483
updateURL: meta.updateURL || '',
1647016484
downloadURL: meta.downloadURL || '',
16471-
supportURL: meta.supportURL || ''
16485+
supportURL: meta.supportURL || '',
16486+
// Phase 11.7 — Userscripts (Safari) injection priority.
16487+
weight: meta.weight || 0,
16488+
priority: meta.priority || 0
1647216489
},
1647316490
scriptMetaStr: ${JSON.stringify(script.code.match(/\/\/\s*==UserScript==([\s\S]*?)\/\/\s*==\/UserScript==/)?.[0] || '')},
1647416491
scriptHandler: 'ScriptVault',

manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"manifest_version": 3,
33
"name": "__MSG_extName__",
4-
"version": "3.4.0",
4+
"version": "3.5.0",
55
"description": "__MSG_extDescription__",
66
"default_locale": "en",
77
"minimum_chrome_version": "120",

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "scriptvault",
3-
"version": "3.4.0",
3+
"version": "3.5.0",
44
"private": true,
55
"type": "module",
66
"scripts": {

src/background/parser.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ export function parseUserscript(code: string): ParseResult {
153153
incompatible: [],
154154
webRequest: null,
155155
priority: 0,
156+
weight: 0,
156157
};
157158

158159
const metaBlock: string = metaBlockMatch[1]!;
@@ -195,6 +196,14 @@ export function parseUserscript(code: string): ParseResult {
195196
case 'priority':
196197
meta.priority = parseInt(value, 10) || 0;
197198
break;
199+
case 'weight': {
200+
// Phase 11.7 — Userscripts (Safari) `@weight 1..999`. Higher =
201+
// earlier within the same `@run-at`. Clamped to the documented
202+
// range so an `@weight 99999` typo can't dominate the sort.
203+
const w: number = parseInt(value, 10);
204+
if (Number.isFinite(w)) meta.weight = Math.max(1, Math.min(999, w));
205+
break;
206+
}
198207
case 'nodownload':
199208
(meta as unknown as { nodownload?: boolean }).nodownload = true;
200209
break;

src/types/script.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ export interface ScriptMeta {
4444
'top-level-await': boolean;
4545
webRequest: WebRequestRule[] | null;
4646
priority: number;
47+
/** Userscripts (Safari) `@weight 1..999` injection priority — higher = earlier. */
48+
weight: number;
4749

4850
// Tags & compat
4951
antifeature: string[];

tests/parser.test.js

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ function parseUserscript(code) {
4747
compatible: [],
4848
incompatible: [],
4949
webRequest: null,
50-
priority: 0
50+
priority: 0,
51+
weight: 0
5152
};
5253

5354
const metaBlock = metaBlockMatch[1];
@@ -126,6 +127,11 @@ function parseUserscript(code) {
126127
case 'priority':
127128
meta.priority = parseInt(value, 10) || 0;
128129
break;
130+
case 'weight': {
131+
const w = parseInt(value, 10);
132+
if (Number.isFinite(w)) meta.weight = Math.max(1, Math.min(999, w));
133+
break;
134+
}
129135
case 'webRequest':
130136
try { meta.webRequest = JSON.parse(value); } catch (e) {}
131137
break;
@@ -290,6 +296,41 @@ describe('parseUserscript', () => {
290296
expect(meta.priority).toBe(0);
291297
});
292298

299+
// Phase 11.7 — @weight (Userscripts/Safari) ─────────────────────────────
300+
it('parses @weight as integer in 1..999 range', () => {
301+
const code = makeScript({ name: 'Wt', weight: '42' });
302+
const { meta } = parseUserscript(code);
303+
expect(meta.weight).toBe(42);
304+
});
305+
306+
it('clamps @weight above 999 down to 999', () => {
307+
const code = makeScript({ name: 'Wt', weight: '99999' });
308+
const { meta } = parseUserscript(code);
309+
expect(meta.weight).toBe(999);
310+
});
311+
312+
it('clamps @weight below 1 up to 1', () => {
313+
const code = makeScript({ name: 'Wt', weight: '0' });
314+
const { meta } = parseUserscript(code);
315+
expect(meta.weight).toBe(1);
316+
const code2 = makeScript({ name: 'Wt', weight: '-50' });
317+
const { meta: meta2 } = parseUserscript(code2);
318+
expect(meta2.weight).toBe(1);
319+
});
320+
321+
it('leaves @weight at default 0 when not specified', () => {
322+
const code = makeScript({ name: 'NoWt' });
323+
const { meta } = parseUserscript(code);
324+
expect(meta.weight).toBe(0);
325+
});
326+
327+
it('ignores non-numeric @weight values', () => {
328+
const code = makeScript({ name: 'Wt', weight: 'oops' });
329+
const { meta } = parseUserscript(code);
330+
// Non-numeric leaves the default 0 in place rather than clamping to 1.
331+
expect(meta.weight).toBe(0);
332+
});
333+
293334
it('parses @resource with name and URL', () => {
294335
const code = makeScript({ name: 'Res', resource: 'css https://example.com/style.css' });
295336
const { meta } = parseUserscript(code);

0 commit comments

Comments
 (0)