From 8e72ca86180e7c2c21fea69ec27b3ba403a15a6b Mon Sep 17 00:00:00 2001 From: pipecraft Date: Sat, 27 Dec 2025 17:18:16 +0800 Subject: [PATCH] v0.1.2 --- .editorconfig | 9 + .gitignore | 9 + .prettierignore | 5 + .prettierrc.cjs | 14 + README.md | 16 +- README.zh-CN.md | 16 +- greasemonkey.md | 2 +- merge-readmes.js | 70 +++++ package.json | 17 ++ pnpm-lock.yaml | 24 ++ quoid-userscripts.md | 2 +- scriptcat.md | 2 +- stay-safari.md | 2 +- tampermonkey.md | 2 +- userscript-api-benchmark.user.js | 431 +++++++++++++++++++++++++------ violentmonkey.md | 4 +- 16 files changed, 529 insertions(+), 96 deletions(-) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 .prettierignore create mode 100644 .prettierrc.cjs create mode 100644 merge-readmes.js create mode 100644 package.json create mode 100644 pnpm-lock.yaml diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c6c8b36 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dcd1c21 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ + +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/coverage + +.DS_Store + diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..09ad833 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,5 @@ +node_modules/** +LICENSE +pnpm-lock.yaml +tsconfig.json +tsconfig.tsbuildinfo diff --git a/.prettierrc.cjs b/.prettierrc.cjs new file mode 100644 index 0000000..d31f391 --- /dev/null +++ b/.prettierrc.cjs @@ -0,0 +1,14 @@ +/** + * @type {import('prettier').Options} + */ +module.exports = { + printWidth: 80, + tabWidth: 2, + useTabs: false, + semi: false, + singleQuote: true, + trailingComma: 'es5', + bracketSpacing: true, + bracketSameLine: true, + overrides: [{ files: '*.json', options: { parser: 'json-stringify' } }], +} diff --git a/README.md b/README.md index f0eb1df..2af6d41 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ It is particularly useful for: | deleteValue | ✅ | 1/1 | ✅ | 1/1 | | listValues | ✅ | 1/1 | ✅ | 1/1 | | setValues / getValues / deleteValues | ✅ | 1/1 | ✅ | 1/1 | -| addValueChangeListener / removeValueChangeListener | ✅ | 1/1 | ✅ | 1/1 | +| addValueChangeListener / removeValueChangeListener | ✅ | 5/5 | ✅ | 5/5 | | addStyle | ✅ | 1/1 | ✅ | 1/1 | | addElement | ✅ | 1/1 | ✅ | 1/1 | | registerMenuCommand | ✅ | 1/1 | ✅ | 1/1 | @@ -78,8 +78,8 @@ It is particularly useful for: | setValue / getValue | ✅ | 2/2 | ✅ | 2/2 | | deleteValue | ✅ | 1/1 | ✅ | 1/1 | | listValues | ✅ | 1/1 | ✅ | 1/1 | -| setValues / getValues / deleteValues | ⚠️ | 0/1 | ✅ | 1/1 | -| addValueChangeListener / removeValueChangeListener | ✅ | 1/1 | ✅ | 1/1 | +| setValues / getValues / deleteValues | ✅ | 1/1 | ✅ | 1/1 | +| addValueChangeListener / removeValueChangeListener | ✅ | 5/5 | ✅ | 5/5 | | addStyle | ✅ | 1/1 | ✅ | 1/1 | | addElement | ✅ | 1/1 | ✅ | 1/1 | | registerMenuCommand | ✅ | 1/1 | ✅ | 1/1 | @@ -114,7 +114,7 @@ It is particularly useful for: | deleteValue | ✅ | 1/1 | ✅ | 1/1 | | listValues | ✅ | 1/1 | ✅ | 1/1 | | setValues / getValues / deleteValues | ✅ | 1/1 | ✅ | 1/1 | -| addValueChangeListener / removeValueChangeListener | ✅ | 1/1 | ✅ | 1/1 | +| addValueChangeListener / removeValueChangeListener | ✅ | 5/5 | ✅ | 5/5 | | addStyle | ✅ | 1/1 | ✅ | 1/1 | | addElement | ✅ | 1/1 | ✅ | 1/1 | | registerMenuCommand | ✅ | 1/1 | ✅ | 1/1 | @@ -149,7 +149,7 @@ It is particularly useful for: | deleteValue | ✅ | 1/1 | ❌ | 0/1 | | listValues | ✅ | 1/1 | ❌ | 0/1 | | setValues / getValues / deleteValues | ❌ | 0/1 | ❌ | 0/1 | -| addValueChangeListener / removeValueChangeListener | ❌ | 0/1 | ❌ | 0/1 | +| addValueChangeListener / removeValueChangeListener | ❌ | 0/5 | ❌ | 0/5 | | addStyle | ✅ | 1/1 | ❌ | 0/1 | | addElement | ❌ | 0/1 | ❌ | 0/1 | | registerMenuCommand | ❌ | 0/1 | ❌ | 0/1 | @@ -184,7 +184,7 @@ It is particularly useful for: | deleteValue | ✅ | 1/1 | ✅ | 1/1 | | listValues | ✅ | 1/1 | ✅ | 1/1 | | setValues / getValues / deleteValues | ❌ | 0/1 | ❌ | 0/1 | -| addValueChangeListener / removeValueChangeListener | ⚠️ | 0/1 | ⚠️ | 0/1 | +| addValueChangeListener / removeValueChangeListener | ⚠️ | 0/5 | ⚠️ | 0/5 | | addStyle | ✅ | 1/1 | ✅ | 1/1 | | addElement | ✅ | 1/1 | ✅ | 1/1 | | registerMenuCommand | ✅ | 1/1 | ✅ | 1/1 | @@ -219,7 +219,7 @@ It is particularly useful for: | deleteValue | ✅ | 1/1 | ❌ | 0/1 | | listValues | ✅ | 1/1 | ❌ | 0/1 | | setValues / getValues / deleteValues | ❌ | 0/1 | ❌ | 0/1 | -| addValueChangeListener / removeValueChangeListener | ❌ | 0/1 | ❌ | 0/1 | +| addValueChangeListener / removeValueChangeListener | ❌ | 0/5 | ❌ | 0/5 | | addStyle | ❌ | 0/1 | ❌ | 0/1 | | addElement | ❌ | 0/1 | ❌ | 0/1 | | registerMenuCommand | ✅ | 1/1 | ❌ | 0/1 | @@ -240,6 +240,8 @@ It is particularly useful for: | window.close | ✅ | 1/1 | - | - | | window.focus | ✅ | 1/1 | - | - | +### END + ## Project Info - **Repository**: [https://github.com/utags/userscripts](https://github.com/utags/userscripts) diff --git a/README.zh-CN.md b/README.zh-CN.md index fc6ea92..c4141ad 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -44,7 +44,7 @@ | deleteValue | ✅ | 1/1 | ✅ | 1/1 | | listValues | ✅ | 1/1 | ✅ | 1/1 | | setValues / getValues / deleteValues | ✅ | 1/1 | ✅ | 1/1 | -| addValueChangeListener / removeValueChangeListener | ✅ | 1/1 | ✅ | 1/1 | +| addValueChangeListener / removeValueChangeListener | ✅ | 5/5 | ✅ | 5/5 | | addStyle | ✅ | 1/1 | ✅ | 1/1 | | addElement | ✅ | 1/1 | ✅ | 1/1 | | registerMenuCommand | ✅ | 1/1 | ✅ | 1/1 | @@ -78,8 +78,8 @@ | setValue / getValue | ✅ | 2/2 | ✅ | 2/2 | | deleteValue | ✅ | 1/1 | ✅ | 1/1 | | listValues | ✅ | 1/1 | ✅ | 1/1 | -| setValues / getValues / deleteValues | ⚠️ | 0/1 | ✅ | 1/1 | -| addValueChangeListener / removeValueChangeListener | ✅ | 1/1 | ✅ | 1/1 | +| setValues / getValues / deleteValues | ✅ | 1/1 | ✅ | 1/1 | +| addValueChangeListener / removeValueChangeListener | ✅ | 5/5 | ✅ | 5/5 | | addStyle | ✅ | 1/1 | ✅ | 1/1 | | addElement | ✅ | 1/1 | ✅ | 1/1 | | registerMenuCommand | ✅ | 1/1 | ✅ | 1/1 | @@ -114,7 +114,7 @@ | deleteValue | ✅ | 1/1 | ✅ | 1/1 | | listValues | ✅ | 1/1 | ✅ | 1/1 | | setValues / getValues / deleteValues | ✅ | 1/1 | ✅ | 1/1 | -| addValueChangeListener / removeValueChangeListener | ✅ | 1/1 | ✅ | 1/1 | +| addValueChangeListener / removeValueChangeListener | ✅ | 5/5 | ✅ | 5/5 | | addStyle | ✅ | 1/1 | ✅ | 1/1 | | addElement | ✅ | 1/1 | ✅ | 1/1 | | registerMenuCommand | ✅ | 1/1 | ✅ | 1/1 | @@ -149,7 +149,7 @@ | deleteValue | ✅ | 1/1 | ❌ | 0/1 | | listValues | ✅ | 1/1 | ❌ | 0/1 | | setValues / getValues / deleteValues | ❌ | 0/1 | ❌ | 0/1 | -| addValueChangeListener / removeValueChangeListener | ❌ | 0/1 | ❌ | 0/1 | +| addValueChangeListener / removeValueChangeListener | ❌ | 0/5 | ❌ | 0/5 | | addStyle | ✅ | 1/1 | ❌ | 0/1 | | addElement | ❌ | 0/1 | ❌ | 0/1 | | registerMenuCommand | ❌ | 0/1 | ❌ | 0/1 | @@ -184,7 +184,7 @@ | deleteValue | ✅ | 1/1 | ✅ | 1/1 | | listValues | ✅ | 1/1 | ✅ | 1/1 | | setValues / getValues / deleteValues | ❌ | 0/1 | ❌ | 0/1 | -| addValueChangeListener / removeValueChangeListener | ⚠️ | 0/1 | ⚠️ | 0/1 | +| addValueChangeListener / removeValueChangeListener | ⚠️ | 0/5 | ⚠️ | 0/5 | | addStyle | ✅ | 1/1 | ✅ | 1/1 | | addElement | ✅ | 1/1 | ✅ | 1/1 | | registerMenuCommand | ✅ | 1/1 | ✅ | 1/1 | @@ -219,7 +219,7 @@ | deleteValue | ✅ | 1/1 | ❌ | 0/1 | | listValues | ✅ | 1/1 | ❌ | 0/1 | | setValues / getValues / deleteValues | ❌ | 0/1 | ❌ | 0/1 | -| addValueChangeListener / removeValueChangeListener | ❌ | 0/1 | ❌ | 0/1 | +| addValueChangeListener / removeValueChangeListener | ❌ | 0/5 | ❌ | 0/5 | | addStyle | ❌ | 0/1 | ❌ | 0/1 | | addElement | ❌ | 0/1 | ❌ | 0/1 | | registerMenuCommand | ✅ | 1/1 | ❌ | 0/1 | @@ -240,6 +240,8 @@ | window.close | ✅ | 1/1 | - | - | | window.focus | ✅ | 1/1 | - | - | +### END + ## 项目信息 - **代码仓库**: [https://github.com/utags/userscripts](https://github.com/utags/userscripts) diff --git a/greasemonkey.md b/greasemonkey.md index c3ead2c..1d80c88 100644 --- a/greasemonkey.md +++ b/greasemonkey.md @@ -12,7 +12,7 @@ | deleteValue | ✅ | 1/1 | ❌ | 0/1 | | listValues | ✅ | 1/1 | ❌ | 0/1 | | setValues / getValues / deleteValues | ❌ | 0/1 | ❌ | 0/1 | -| addValueChangeListener / removeValueChangeListener | ❌ | 0/1 | ❌ | 0/1 | +| addValueChangeListener / removeValueChangeListener | ❌ | 0/5 | ❌ | 0/5 | | addStyle | ❌ | 0/1 | ❌ | 0/1 | | addElement | ❌ | 0/1 | ❌ | 0/1 | | registerMenuCommand | ✅ | 1/1 | ❌ | 0/1 | diff --git a/merge-readmes.js b/merge-readmes.js new file mode 100644 index 0000000..c94aa38 --- /dev/null +++ b/merge-readmes.js @@ -0,0 +1,70 @@ +const fs = require('fs') +const path = require('path') + +const baseDir = __dirname +const readmes = ['README.md', 'README.zh-CN.md'].map((p) => + path.join(baseDir, p) +) +const sources = [ + { file: 'tampermonkey.md', heading: /^###\s*Tampermonkey\b/ }, + { file: 'violentmonkey.md', heading: /^###\s*Violentmonkey\b/ }, + { file: 'scriptcat.md', heading: /^###\s*ScriptCat\b/ }, + { file: 'greasemonkey.md', heading: /^###\s*Greasemonkey\b/ }, + { + file: 'quoid-userscripts.md', + heading: /^###\s*Userscripts\s*\(Safari\)/, + }, + { file: 'stay-safari.md', heading: /^###\s*Stay\s*\(Safari\)/ }, +] + +function readBody(filePath) { + const content = fs.readFileSync(filePath, 'utf8') + const lines = content.split(/\r?\n/) + if (lines.length && lines[0].trim().startsWith('#')) lines.shift() + return lines.join('\n').trim() + '\n' +} + +function replaceSection(doc, headingRegex, newBody) { + const lines = doc.split(/\r?\n/) + let hIndex = -1 + for (let i = 0; i < lines.length; i++) { + if (headingRegex.test(lines[i])) { + hIndex = i + break + } + } + if (hIndex === -1) return doc + let end = lines.length + for (let j = hIndex + 1; j < lines.length; j++) { + if (/^###\s/.test(lines[j])) { + end = j + break + } + } + const prefix = lines.slice(0, hIndex + 1).join('\n') + const suffix = lines.slice(end).join('\n') + const body = newBody.replace(/\r?\n$/, '') + '\n' + return prefix + '\n' + body + suffix +} + +function run() { + const newBodies = {} + for (const { file, heading } of sources) { + const srcPath = path.join(baseDir, file) + if (fs.existsSync(srcPath)) { + newBodies[file] = { heading, body: readBody(srcPath) } + } + } + for (const readmePath of readmes) { + let doc = fs.readFileSync(readmePath, 'utf8') + for (const { file } of sources) { + const entry = newBodies[file] + if (entry) { + doc = replaceSection(doc, entry.heading, entry.body) + } + } + fs.writeFileSync(readmePath, doc, 'utf8') + } +} + +run() diff --git a/package.json b/package.json new file mode 100644 index 0000000..5294136 --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "userscript-api-benchmark", + "version": "0.1.2", + "description": "This script is a comprehensive benchmark tool designed to test the compatibility and accuracy of Userscript Manager APIs.", + "main": "userscript-api-benchmark.user.js", + "scripts": { + "format": "prettier --write .", + "merge-readmes": "node merge-readmes.js && pnpm run format", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "Pipecraft", + "license": "MIT", + "devDependencies": { + "prettier": "^3.7.4" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..2a14c5e --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,24 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + prettier: + specifier: ^3.7.4 + version: 3.7.4 + +packages: + + prettier@3.7.4: + resolution: {integrity: sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==} + engines: {node: '>=14'} + hasBin: true + +snapshots: + + prettier@3.7.4: {} diff --git a/quoid-userscripts.md b/quoid-userscripts.md index 07807aa..1e4a5cb 100644 --- a/quoid-userscripts.md +++ b/quoid-userscripts.md @@ -12,7 +12,7 @@ | deleteValue | ✅ | 1/1 | ❌ | 0/1 | | listValues | ✅ | 1/1 | ❌ | 0/1 | | setValues / getValues / deleteValues | ❌ | 0/1 | ❌ | 0/1 | -| addValueChangeListener / removeValueChangeListener | ❌ | 0/1 | ❌ | 0/1 | +| addValueChangeListener / removeValueChangeListener | ❌ | 0/5 | ❌ | 0/5 | | addStyle | ✅ | 1/1 | ❌ | 0/1 | | addElement | ❌ | 0/1 | ❌ | 0/1 | | registerMenuCommand | ❌ | 0/1 | ❌ | 0/1 | diff --git a/scriptcat.md b/scriptcat.md index 489a588..55cdaa3 100644 --- a/scriptcat.md +++ b/scriptcat.md @@ -12,7 +12,7 @@ | deleteValue | ✅ | 1/1 | ✅ | 1/1 | | listValues | ✅ | 1/1 | ✅ | 1/1 | | setValues / getValues / deleteValues | ✅ | 1/1 | ✅ | 1/1 | -| addValueChangeListener / removeValueChangeListener | ✅ | 1/1 | ✅ | 1/1 | +| addValueChangeListener / removeValueChangeListener | ✅ | 5/5 | ✅ | 5/5 | | addStyle | ✅ | 1/1 | ✅ | 1/1 | | addElement | ✅ | 1/1 | ✅ | 1/1 | | registerMenuCommand | ✅ | 1/1 | ✅ | 1/1 | diff --git a/stay-safari.md b/stay-safari.md index 02be5a9..2b6c22f 100644 --- a/stay-safari.md +++ b/stay-safari.md @@ -12,7 +12,7 @@ | deleteValue | ✅ | 1/1 | ✅ | 1/1 | | listValues | ✅ | 1/1 | ✅ | 1/1 | | setValues / getValues / deleteValues | ❌ | 0/1 | ❌ | 0/1 | -| addValueChangeListener / removeValueChangeListener | ⚠️ | 0/1 | ⚠️ | 0/1 | +| addValueChangeListener / removeValueChangeListener | ⚠️ | 0/5 | ⚠️ | 0/5 | | addStyle | ✅ | 1/1 | ✅ | 1/1 | | addElement | ✅ | 1/1 | ✅ | 1/1 | | registerMenuCommand | ✅ | 1/1 | ✅ | 1/1 | diff --git a/tampermonkey.md b/tampermonkey.md index de573e4..74d91da 100644 --- a/tampermonkey.md +++ b/tampermonkey.md @@ -12,7 +12,7 @@ | deleteValue | ✅ | 1/1 | ✅ | 1/1 | | listValues | ✅ | 1/1 | ✅ | 1/1 | | setValues / getValues / deleteValues | ✅ | 1/1 | ✅ | 1/1 | -| addValueChangeListener / removeValueChangeListener | ✅ | 1/1 | ✅ | 1/1 | +| addValueChangeListener / removeValueChangeListener | ✅ | 5/5 | ✅ | 5/5 | | addStyle | ✅ | 1/1 | ✅ | 1/1 | | addElement | ✅ | 1/1 | ✅ | 1/1 | | registerMenuCommand | ✅ | 1/1 | ✅ | 1/1 | diff --git a/userscript-api-benchmark.user.js b/userscript-api-benchmark.user.js index 922cf58..6dac4e4 100644 --- a/userscript-api-benchmark.user.js +++ b/userscript-api-benchmark.user.js @@ -2,26 +2,29 @@ // @name Userscript API Benchmark // @name:zh-CN 用户脚本 API 基准测试 // @namespace https://github.com/utags/userscripts -// @version 0.1.1 +// @version 0.1.2 // @description Comprehensive benchmark tool for UserScript Manager APIs (GM.* and GM_*) // @description:zh-CN 用户脚本管理器 API (GM.* 和 GM_*) 的综合基准测试工具,用于检查兼容性与准确性 // @author Pipecraft -// @noframes // @match *://*/* // @grant unsafeWindow // @grant window.close // @grant window.focus // @grant window.onurlchange +// @grant GM_addValueChangeListener +// @grant GM_removeValueChangeListener +// @grant GM_setValue +// @grant GM_deleteValue +// @grant GM.addValueChangeListener +// @grant GM.removeValueChangeListener +// @grant GM.setValue +// @grant GM.deleteValue // @grant GM_info // @grant GM.info // @grant GM_log // @grant GM.log -// @grant GM_setValue // @grant GM_getValue -// @grant GM_deleteValue -// @grant GM.setValue // @grant GM.getValue -// @grant GM.deleteValue // @grant GM_listValues // @grant GM.listValues // @grant GM_setValues @@ -30,10 +33,6 @@ // @grant GM.setValues // @grant GM.getValues // @grant GM.deleteValues -// @grant GM_addValueChangeListener -// @grant GM_removeValueChangeListener -// @grant GM.addValueChangeListener -// @grant GM.removeValueChangeListener // @grant GM_addStyle // @grant GM.addStyle // @grant GM_addElement @@ -72,6 +71,318 @@ // ;(() => { 'use strict' + function registerValueChangeListenerTests(registerTest2) { + registerTest2( + 'addValueChangeListener / removeValueChangeListener', + async () => { + if ( + typeof GM_addValueChangeListener !== 'function' || + typeof GM_removeValueChangeListener !== 'function' || + typeof GM_setValue !== 'function' || + typeof GM_deleteValue !== 'function' + ) { + return { supported: false, passed: 0, total: 5 } + } + let passed = 0 + const total = 5 + const messages = [] + { + const key = 'benchmark_listener_basic_gm' + let triggered = false + const id = GM_addValueChangeListener( + key, + (name, oldVal, newVal, remote) => { + if (name === key && newVal === 'changed') { + triggered = true + } + } + ) + GM_setValue(key, 'changed') + await new Promise((resolve) => { + setTimeout(resolve, 200) + }) + GM_removeValueChangeListener(id) + GM_deleteValue(key) + if (triggered) { + passed++ + } else { + messages.push('Basic: Not triggered') + } + } + { + const key = 'benchmark_listener_same_gm' + GM_deleteValue(key) + let triggered = false + const id = GM_addValueChangeListener( + key, + (name, oldVal, newVal, remote) => { + if (name === key) triggered = true + } + ) + GM_setValue(key, 'initial') + await new Promise((resolve) => { + setTimeout(resolve, 200) + }) + const initialTriggered = triggered + if (!initialTriggered) { + messages.push('SameValue: Initial change NOT triggered') + } + triggered = false + GM_setValue(key, 'initial') + await new Promise((resolve) => { + setTimeout(resolve, 200) + }) + GM_removeValueChangeListener(id) + GM_deleteValue(key) + if (triggered) { + messages.push('SameValue: Triggered unexpectedly on same value') + } + if (initialTriggered && !triggered) { + passed++ + } + } + { + const key = 'benchmark_listener_obj_gm' + const obj = { foo: 'bar', num: 123 } + let receivedVal = null + const id = GM_addValueChangeListener( + key, + (name, oldVal, newVal, remote) => { + if (name === key) receivedVal = newVal + } + ) + GM_setValue(key, obj) + await new Promise((resolve) => { + setTimeout(resolve, 200) + }) + GM_removeValueChangeListener(id) + GM_deleteValue(key) + const objPassed = + receivedVal && + typeof receivedVal === 'object' && + receivedVal.foo === 'bar' && + receivedVal.num === 123 + if (objPassed) { + passed++ + } else { + messages.push('Object: Value mismatch') + } + } + { + const keyLocal = 'benchmark_listener_remote_local_gm' + const keyRemote = 'benchmark_listener_remote_other_gm' + let localRemoteFlag + let otherRemoteFlag + const id1 = GM_addValueChangeListener( + keyLocal, + (name, oldVal, newVal, remote) => { + if (name === keyLocal) localRemoteFlag = remote + } + ) + GM_setValue(keyLocal, 'local_change') + await new Promise((resolve) => { + setTimeout(resolve, 200) + }) + GM_removeValueChangeListener(id1) + GM_deleteValue(keyLocal) + const id2 = GM_addValueChangeListener( + keyRemote, + (name, oldVal, newVal, remote) => { + if (name === keyRemote) otherRemoteFlag = remote + } + ) + const iframe = document.createElement('iframe') + const url = new URL(location.href) + url.searchParams.set('benchmark_role', 'iframe') + url.searchParams.set('key', keyRemote) + url.searchParams.set('value', 'remote_change') + iframe.src = url.href + iframe.style.display = 'none' + document.body.append(iframe) + await new Promise((resolve) => { + setTimeout(resolve, 1e3) + }) + iframe.remove() + GM_removeValueChangeListener(id2) + GM_deleteValue(keyRemote) + if (localRemoteFlag === false) { + passed++ + } else { + messages.push( + 'Remote(Local): Expected false, got '.concat(localRemoteFlag) + ) + } + if (otherRemoteFlag === true) { + passed++ + } else { + messages.push( + 'Remote(Other): Expected true, got '.concat(otherRemoteFlag) + ) + } + } + return { + supported: true, + passed, + total, + message: messages.length > 0 ? messages.join('; ') : void 0, + } + }, + async () => { + if ( + typeof GM === 'undefined' || + typeof GM.addValueChangeListener !== 'function' || + typeof GM.removeValueChangeListener !== 'function' || + typeof GM.setValue !== 'function' || + typeof GM.deleteValue !== 'function' + ) { + return { supported: false, passed: 0, total: 5 } + } + let passed = 0 + const total = 5 + const messages = [] + { + const key = 'benchmark_listener_basic_gm_dot' + let triggered = false + const id = await GM.addValueChangeListener( + key, + (name, oldVal, newVal, remote) => { + if (name === key && newVal === 'changed') { + triggered = true + } + } + ) + await GM.setValue(key, 'changed') + await new Promise((resolve) => { + setTimeout(resolve, 200) + }) + await GM.removeValueChangeListener(id) + await GM.deleteValue(key) + if (triggered) { + passed++ + } else { + messages.push('Basic: Not triggered') + } + } + { + const key = 'benchmark_listener_same_gm_dot' + await GM.deleteValue(key) + let triggered = false + const id = await GM.addValueChangeListener( + key, + (name, oldVal, newVal, remote) => { + if (name === key) triggered = true + } + ) + await GM.setValue(key, 'initial') + await new Promise((resolve) => { + setTimeout(resolve, 200) + }) + const initialTriggered = triggered + if (!initialTriggered) { + messages.push('SameValue: Initial change NOT triggered') + } + triggered = false + await GM.setValue(key, 'initial') + await new Promise((resolve) => { + setTimeout(resolve, 200) + }) + await GM.removeValueChangeListener(id) + await GM.deleteValue(key) + if (triggered) { + messages.push('SameValue: Triggered unexpectedly on same value') + } + if (initialTriggered && !triggered) { + passed++ + } + } + { + const key = 'benchmark_listener_obj_gm_dot' + const obj = { foo: 'bar', num: 123 } + let receivedVal = null + const id = await GM.addValueChangeListener( + key, + (name, oldVal, newVal, remote) => { + if (name === key) receivedVal = newVal + } + ) + await GM.setValue(key, obj) + await new Promise((resolve) => { + setTimeout(resolve, 200) + }) + await GM.removeValueChangeListener(id) + await GM.deleteValue(key) + const objPassed = + receivedVal && + typeof receivedVal === 'object' && + receivedVal.foo === 'bar' && + receivedVal.num === 123 + if (objPassed) { + passed++ + } else { + messages.push('Object: Value mismatch') + } + } + { + const keyLocal = 'benchmark_listener_remote_local_gm_dot' + const keyRemote = 'benchmark_listener_remote_other_gm_dot' + let localRemoteFlag + let otherRemoteFlag + const id1 = await GM.addValueChangeListener( + keyLocal, + (name, oldVal, newVal, remote) => { + if (name === keyLocal) localRemoteFlag = remote + } + ) + await GM.setValue(keyLocal, 'local_change') + await new Promise((resolve) => { + setTimeout(resolve, 200) + }) + await GM.removeValueChangeListener(id1) + await GM.deleteValue(keyLocal) + const id2 = await GM.addValueChangeListener( + keyRemote, + (name, oldVal, newVal, remote) => { + if (name === keyRemote) otherRemoteFlag = remote + } + ) + const iframe = document.createElement('iframe') + const url = new URL(location.href) + url.searchParams.set('benchmark_role', 'iframe') + url.searchParams.set('key', keyRemote) + url.searchParams.set('value', 'remote_change') + iframe.src = url.href + iframe.style.display = 'none' + document.body.append(iframe) + await new Promise((resolve) => { + setTimeout(resolve, 1e3) + }) + iframe.remove() + await GM.removeValueChangeListener(id2) + await GM.deleteValue(keyRemote) + if (localRemoteFlag === false) { + passed++ + } else { + messages.push( + 'Remote(Local): Expected false, got '.concat(localRemoteFlag) + ) + } + if (otherRemoteFlag === true) { + passed++ + } else { + messages.push( + 'Remote(Other): Expected true, got '.concat(otherRemoteFlag) + ) + } + } + return { + supported: true, + passed, + total, + message: messages.length > 0 ? messages.join('; ') : void 0, + } + } + ) + } var tests = [] function registerTest(name, gmRun, gmDotRun) { tests.push({ name, gmRun, gmDotRun }) @@ -244,15 +555,16 @@ return { supported: false, passed: 0, total: 1 } } const data = { - benchmark_gm_key1: 'val1-' + Math.random(), - benchmark_gm_key2: 12345, + benchmark_set_values_key1: 'val1-' + Math.random(), + benchmark_set_values_key2: 12345, } await GM_setValues(data) const retrieved = await GM_getValues(Object.keys(data)) await GM_deleteValues(Object.keys(data)) const passed = - retrieved.benchmark_gm_key1 === data.benchmark_gm_key1 && - retrieved.benchmark_gm_key2 === data.benchmark_gm_key2 + retrieved.benchmark_set_values_key1 === + data.benchmark_set_values_key1 && + retrieved.benchmark_set_values_key2 === data.benchmark_set_values_key2 return { supported: true, passed: passed ? 1 : 0, total: 1 } }, async () => { @@ -265,74 +577,21 @@ return { supported: false, passed: 0, total: 1 } } const data = { - benchmark_gm_key1: 'val1-' + Math.random(), - benchmark_gm_key2: 12345, + benchmark_set_values_gm4_key1: 'val1-' + Math.random(), + benchmark_set_values_gm4_key2: 12345, } await GM.setValues(data) const retrieved = await GM.getValues(Object.keys(data)) await GM.deleteValues(Object.keys(data)) const passed = - retrieved.benchmark_gm_key1 === data.benchmark_gm_key1 && - retrieved.benchmark_gm_key2 === data.benchmark_gm_key2 + retrieved.benchmark_set_values_gm4_key1 === + data.benchmark_set_values_gm4_key1 && + retrieved.benchmark_set_values_gm4_key2 === + data.benchmark_set_values_gm4_key2 return { supported: true, passed: passed ? 1 : 0, total: 1 } } ) - registerTest( - 'addValueChangeListener / removeValueChangeListener', - async () => { - if ( - typeof GM_addValueChangeListener !== 'function' || - typeof GM_removeValueChangeListener !== 'function' || - typeof GM_setValue !== 'function' - ) { - return { supported: false, passed: 0, total: 1 } - } - const key = 'benchmark_listener_key' - let triggered = false - const id = GM_addValueChangeListener( - key, - (name, oldVal, newVal, remote) => { - if (name === key && newVal === 'changed') { - triggered = true - } - } - ) - await GM_setValue(key, 'changed') - await new Promise((resolve) => { - setTimeout(resolve, 200) - }) - GM_removeValueChangeListener(id) - await GM_deleteValue(key) - return { supported: true, passed: triggered ? 1 : 0, total: 1 } - }, - async () => { - if ( - typeof GM === 'undefined' || - typeof GM.addValueChangeListener !== 'function' || - typeof GM.removeValueChangeListener !== 'function' || - typeof GM.setValue !== 'function' - ) { - return { supported: false, passed: 0, total: 1 } - } - const key = 'benchmark_listener_key' - let triggered = false - const id = await GM.addValueChangeListener( - key, - (name, oldVal, newVal, remote) => { - if (name === key && newVal === 'changed') { - triggered = true - } - } - ) - await GM.setValue(key, 'changed') - await new Promise((resolve) => { - setTimeout(resolve, 200) - }) - await GM.removeValueChangeListener(id) - await GM.deleteValue(key) - return { supported: true, passed: triggered ? 1 : 0, total: 1 } - } - ) + registerValueChangeListenerTests(registerTest) registerTest( 'addStyle', () => { @@ -636,7 +895,7 @@ const shadow = host.attachShadow({ mode: 'open' }) const style = document.createElement('style') style.textContent = - '\n :host {\n position: fixed; top: 20px; right: 20px; z-index: 2147483647;\n background: #fff; color: #333; padding: 16px; border-radius: 8px;\n box-shadow: 0 4px 12px rgba(0,0,0,0.2); font-family: sans-serif;\n max-height: 90vh; overflow-y: auto; width: 600px;\n font-size: 13px;\n }\n table { width: 100%; border-collapse: collapse; margin-top: 10px; }\n th, td { border: 1px solid #eee; padding: 6px 8px; text-align: left; }\n th { background: #f9f9f9; font-weight: 600; }\n .pass { color: #2ecc71; font-weight: bold; }\n .fail { color: #e74c3c; font-weight: bold; }\n .na { color: #f59e0b; font-weight: bold; }\n .header h3 { margin: 0 0 8px 0; font-size: 16px; }\n .close { position: absolute; top: 10px; right: 10px; cursor: pointer; font-size: 16px; color: #999; }\n .close:hover { color: #333; }\n .copy-btn {\n position: absolute; top: 10px; right: 40px;\n cursor: pointer; font-size: 13px; color: #007aff; border: 1px solid #007aff;\n padding: 2px 8px; border-radius: 4px; background: transparent;\n }\n .copy-btn:hover { background: #007aff; color: #fff; }\n .copy-btn:active { transform: translateY(1px); }\n .log-area {\n margin-top: 16px;\n padding: 10px;\n background: #f5f5f5;\n border: 1px solid #ddd;\n border-radius: 4px;\n font-family: monospace;\n font-size: 11px;\n max-height: 150px;\n overflow-y: auto;\n white-space: pre-wrap;\n }\n .log-entry { margin-bottom: 4px; border-bottom: 1px solid #eee; padding-bottom: 4px; }\n .log-entry:last-child { border-bottom: none; margin-bottom: 0; }\n .log-entry.error { color: #e74c3c; }\n ' + '\n :host {\n position: fixed; top: 20px; right: 20px; z-index: 2147483647;\n background: #fff; color: #333; padding: 16px; border-radius: 8px;\n box-shadow: 0 4px 12px rgba(0,0,0,0.2); font-family: sans-serif;\n max-height: 90vh; overflow-y: auto; width: 600px;\n font-size: 13px;\n }\n table { width: 100%; border-collapse: collapse; margin-top: 10px; }\n th, td { border: 1px solid #eee; padding: 6px 8px; text-align: left; }\n th { background: #f9f9f9; font-weight: 600; }\n .pass { color: #2ecc71; font-weight: bold; }\n .fail { color: #e74c3c; font-weight: bold; }\n .na { color: #f59e0b; font-weight: bold; }\n .header h3 { margin: 0 0 8px 0; font-size: 16px; }\n .close { position: absolute; top: 10px; right: 10px; cursor: pointer; font-size: 16px; color: #999; }\n .close:hover { color: #333; }\n .copy-btn {\n position: absolute; top: 10px; right: 40px;\n cursor: pointer; font-size: 13px; color: #007aff; border: 1px solid #007aff;\n padding: 2px 8px; border-radius: 4px; background: transparent;\n }\n .copy-btn:hover { background: #007aff; color: #fff; }\n .copy-btn:active { transform: translateY(1px); }\n .log-area {\n margin-top: 16px;\n padding: 10px;\n background: #f5f5f5;\n border: 1px solid #ddd;\n border-radius: 4px;\n font-family: monospace;\n font-size: 11px;\n max-height: 150px;\n overflow-y: auto;\n white-space: pre-wrap;\n }\n .log-entry { margin-bottom: 4px; border-bottom: 1px solid #eee; padding-bottom: 4px; }\n .log-entry:last-child { border-bottom: none; margin-bottom: 0; }\n .log-entry.error { color: #e74c3c; }\n .log-entry.warning { color: #f59e0b; }\n ' shadow.append(style) const wrapper = document.createElement('div') let handler = 'Unknown' @@ -814,11 +1073,21 @@ appendLog(msg, 'error') console.error(msg, gmRes.error) } + if (gmRes.message && gmRes.message !== 'N/A') { + const msg = ''.concat(t.name, ' (GM_): ').concat(gmRes.message) + appendLog(msg, 'warning') + console.warn(msg) + } if (gmDotRes.error) { const msg = ''.concat(t.name, ' (GM.): ').concat(String(gmDotRes.error)) appendLog(msg, 'error') console.error(msg, gmDotRes.error) } + if (gmDotRes.message && gmDotRes.message !== 'N/A') { + const msg = ''.concat(t.name, ' (GM.): ').concat(gmDotRes.message) + appendLog(msg, 'warning') + console.warn(msg) + } const isWindowApi = [ 'unsafeWindow', 'window.onurlchange', @@ -859,6 +1128,18 @@ void render() } function main() { + const urlParams = new URLSearchParams(globalThis.location.search) + if (urlParams.get('benchmark_role') === 'iframe') { + const key = urlParams.get('key') + const value = urlParams.get('value') + if (key && value) { + if (typeof GM_setValue === 'function') GM_setValue(key, value) + if (typeof GM !== 'undefined' && typeof GM.setValue === 'function') { + void GM.setValue(key, value) + } + } + return + } try { const de = document.documentElement if (de && de.dataset && de.dataset.uab === '1') return diff --git a/violentmonkey.md b/violentmonkey.md index 46ba445..9f50fd9 100644 --- a/violentmonkey.md +++ b/violentmonkey.md @@ -11,8 +11,8 @@ | setValue / getValue | ✅ | 2/2 | ✅ | 2/2 | | deleteValue | ✅ | 1/1 | ✅ | 1/1 | | listValues | ✅ | 1/1 | ✅ | 1/1 | -| setValues / getValues / deleteValues | ⚠️ | 0/1 | ✅ | 1/1 | -| addValueChangeListener / removeValueChangeListener | ✅ | 1/1 | ✅ | 1/1 | +| setValues / getValues / deleteValues | ✅ | 1/1 | ✅ | 1/1 | +| addValueChangeListener / removeValueChangeListener | ✅ | 5/5 | ✅ | 5/5 | | addStyle | ✅ | 1/1 | ✅ | 1/1 | | addElement | ✅ | 1/1 | ✅ | 1/1 | | registerMenuCommand | ✅ | 1/1 | ✅ | 1/1 |