Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 29 additions & 2 deletions example/gm_add_element.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,36 @@
* 2. 元素标签名
* 3. 属性对象
*/
const el = GM_addElement(document.querySelector('.BorderGrid-cell'), "img", {
src: "https://bbs.tampermonkey.net.cn/uc_server/avatar.php?uid=4&size=small&ts=1"

// ------------- 基础用法 ----------------

const el = GM_addElement(document.querySelector(".BorderGrid-cell"), "img", {
src: "https://bbs.tampermonkey.net.cn/uc_server/avatar.php?uid=4&size=small&ts=1",
});

// 打印创建出来的 DOM 元素
console.log(el);

// ------------- 基础用法 - textContent ----------------

const span3 = GM_addElement("span", {
textContent: "Hello",
});

console.log(`span text: ${span3.textContent}`);

// ------------- 基础用法 - onload & onerror ----------------

new Promise((resolve, reject) => {
img = GM_addElement(document.body, "img", {
src: "https://www.tampermonkey.net/favicon.ico",
onload: resolve,
onerror: reject,
});
})
.then(() => {
console.log("img insert ok");
})
.catch(() => {
console.log("img insert failed");
});
135 changes: 82 additions & 53 deletions example/tests/gm_api_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
// @run-at document-start
// ==/UserScript==

(async function () {
(function () {
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

在示例代码中,将顶层 async IIFE 改为普通 IIFE 可能会导致问题。代码中有 await new Promise(...) 调用(第 279 行),但函数不再是 async,这会导致语法错误。

需要保持函数为 async 或将 await 改为 .then() 链式调用。

Suggested change
(function () {
(async function () {

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

还是恢复原来的写法了

"use strict";

console.log("%c=== ScriptCat GM API 测试开始 ===", "color: blue; font-size: 16px; font-weight: bold;");
Expand Down Expand Up @@ -145,8 +145,44 @@
assert("not_found", GM_getValue("test_delete", "not_found"), "值应该被删除");
});

// ============ GM_addValueChangeListener 测试 ============
await (async () => {
// ============ GM_addStyle 测试 ============
console.log("\n%c--- GM 样式 API 测试 ---", "color: orange; font-weight: bold;");

test("GM_addStyle - CSS字符串", () => {
const css = `
.scriptcat-test {
color: red;
font-weight: bold;
}
`;
const element = GM_addStyle(css);
assert(true, element && element.tagName === "STYLE", "应该返回 style 元素");
console.log("添加的样式元素:", element);
});

// ============ GM_getResourceText/URL 测试 ============
console.log("\n%c--- GM 资源 API 测试 ---", "color: orange; font-weight: bold;");

test("GM_getResourceText", () => {
assert("function", typeof GM_getResourceText, "GM_getResourceText 应该是函数");

const css = GM_getResourceText("testCSS");
assert("string", typeof css, "应该返回字符串");
assert(163870, css.length, "资源内容长度应该是 163870");
console.log("资源文本长度:", css.length);
});

test("GM_getResourceURL", () => {
assert("function", typeof GM_getResourceURL, "GM_getResourceURL 应该是函数");

const url = GM_getResourceURL("testCSS");
assert("string", typeof url, "应该返回字符串");
assert(true, url.startsWith("data:") || url.startsWith("blob:"), "应该返回 data URL 或 blob URL");
console.log("资源 URL:", url.substring(0, 50) + "...");
});

(async () => {
// ============ GM_addValueChangeListener 测试 ============
await testAsync("GM_addValueChangeListener", () => {
return new Promise(async (resolve, reject) => {
let listenerId = null;
Expand Down Expand Up @@ -216,63 +252,56 @@
}, 50);
});
});
})();

// ============ GM_addStyle 测试 ============
console.log("\n%c--- GM 样式 API 测试 ---", "color: orange; font-weight: bold;");
// ============ GM_addElement 测试 ============
await testAsync("GM_addElement - 创建元素", async () => {
assert("function", typeof GM_addElement, "GM_addElement 应该是函数");

test("GM_addStyle - CSS字符串", () => {
const css = `
.scriptcat-test {
color: red;
font-weight: bold;
}
`;
const element = GM_addStyle(css);
assert(true, element && element.tagName === "STYLE", "应该返回 style 元素");
console.log("添加的样式元素:", element);
});
const div = GM_addElement("div", {
textContent: "ScriptCat GM_addElement 测试",
style: "position: fixed; top: 10px; right: 10px; background: yellow; padding: 10px; z-index: 9999;",
});
assert(true, div && div.tagName === "DIV", "应该返回 div 元素");
console.log("添加的元素:", div);

// ============ GM_addElement 测试 ============
test("GM_addElement - 创建元素", () => {
assert("function", typeof GM_addElement, "GM_addElement 应该是函数");
// 创建脚本元素测试
const script = GM_addElement("script", {
textContent: 'window.foo = "bar";',
});
assert(true, script && script.tagName === "SCRIPT", "应该返回 script 元素");
assert("bar", unsafeWindow.foo, "脚本内容应该执行,unsafeWindow.foo 应该是 'bar'");
console.log("添加的脚本元素:", script);

document.querySelector(".container").insertBefore(script, document.querySelector(".masthead"));

// onload 和 onerror 测试 - 插入图片元素
let img;
await new Promise((resolve, reject) => {
img = GM_addElement(document.body, "img", {
src: "https://www.tampermonkey.net/favicon.ico",
onload: () => {
console.log("图片加载成功");
resolve();
},
onerror: (error) => {
reject(new Error("图片加载失败: " + error));
},
});
});
assert(true, img && img.tagName === "IMG", "应该返回 img 元素");
console.log("添加的图片元素:", img);

const div = GM_addElement("div", {
textContent: "ScriptCat GM_addElement 测试",
style: "position: fixed; top: 10px; right: 10px; background: yellow; padding: 10px; z-index: 9999;",
// 3秒后移除
setTimeout(() => {
script.remove();
div.remove();
img.remove();
}, 3000);
});
assert(true, div && div.tagName === "DIV", "应该返回 div 元素");
console.log("添加的元素:", div);

// 3秒后移除
setTimeout(() => div.remove(), 3000);
});

// ============ GM_getResourceText/URL 测试 ============
console.log("\n%c--- GM 资源 API 测试 ---", "color: orange; font-weight: bold;");
// ============ GM_xmlhttpRequest 测试 ============
console.log("\n%c--- GM 网络请求 API 测试 ---", "color: orange; font-weight: bold;");

test("GM_getResourceText", () => {
assert("function", typeof GM_getResourceText, "GM_getResourceText 应该是函数");

const css = GM_getResourceText("testCSS");
assert("string", typeof css, "应该返回字符串");
assert(163870, css.length, "资源内容长度应该是 163870");
console.log("资源文本长度:", css.length);
});

test("GM_getResourceURL", () => {
assert("function", typeof GM_getResourceURL, "GM_getResourceURL 应该是函数");

const url = GM_getResourceURL("testCSS");
assert("string", typeof url, "应该返回字符串");
assert(true, url.startsWith("data:") || url.startsWith("blob:"), "应该返回 data URL 或 blob URL");
console.log("资源 URL:", url.substring(0, 50) + "...");
});

// ============ GM_xmlhttpRequest 测试 ============
console.log("\n%c--- GM 网络请求 API 测试 ---", "color: orange; font-weight: bold;");

(async () => {
await testAsync("GM_xmlhttpRequest - GET 请求", () => {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
Expand Down
4 changes: 2 additions & 2 deletions packages/message/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ export const pageDispatchEvent = performanceClone.dispatchEvent.bind(performance
export const pageAddEventListener = performanceClone.addEventListener.bind(performanceClone);
export const pageRemoveEventListener = performanceClone.removeEventListener.bind(performanceClone);
const detailClone = typeof cloneInto === "function" ? cloneInto : null;
export const pageDispatchCustomEvent = (eventType: string, detail: any) => {
if (detailClone && detail) detail = detailClone(detail, performanceClone);
export const pageDispatchCustomEvent = <T = any>(eventType: string, detail: T) => {
if (detailClone && detail) detail = <T>detailClone(detail, performanceClone);
const ev = new CustomEventClone(eventType, {
detail,
cancelable: true,
Expand Down
1 change: 1 addition & 0 deletions packages/message/custom_event_message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export class CustomEventMessage implements Message {
event.preventDefault(); // 告知另一端这边已准备好
this.readyWrap.setReady(); // 两端已准备好,则 setReady()
} else if (event instanceof MouseEventClone && event.movementX && event.relatedTarget) {
if (event.cancelable) event.preventDefault(); // 告知另一端
relatedTargetMap.set(event.movementX, event.relatedTarget);
} else if (event instanceof CustomEventClone) {
this.messageHandle(event.detail, new CustomEventPostMessage(this));
Expand Down
2 changes: 2 additions & 0 deletions src/app/service/content/create_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const createContext = (
GMInfo: any,
envPrefix: string,
message: Message,
contentMsg: Message,
scriptGrants: Set<string>
) => {
// 按照GMApi构建
Expand All @@ -31,6 +32,7 @@ export const createContext = (
const context = createGMBase({
prefix: envPrefix,
message,
contentMsg,
scriptRes,
valueChangeListener,
EE,
Expand Down
Loading
Loading