Skip to content

[v1.3] 配合 1.3 scripting, 重构 GM_addElement (bug 修补 + 功能改进)#1233

Open
cyfung1031 wants to merge 9 commits intoscriptscat:release/v1.3from
cyfung1031:pr-fix-addElement_bug-301
Open

[v1.3] 配合 1.3 scripting, 重构 GM_addElement (bug 修补 + 功能改进)#1233
cyfung1031 wants to merge 9 commits intoscriptscat:release/v1.3from
cyfung1031:pr-fix-addElement_bug-301

Conversation

@cyfung1031
Copy link
Collaborator

@cyfung1031 cyfung1031 commented Feb 11, 2026

事原

  • 1.3 scripting - scripting.js 不能在 CSP TTP 插入元素执行代码
  • GM_addElement - 最近支持了 onload onerror 等 function value

改善

  • 统一以 content.js 处理 CSP插入元素
  • 【新功能】可使用 content.js 或 inject.js 创造元素 ( native: true )
  • 【新功能】 追加多一个 parameter 让 GM_addElement 可以像 insertBefore 一样插在特定位置
  • 文字 / 数字 均使用 content.js 设置 (避开 TTP )
  • 除了 textContent, 还支持了 innerHTML, outerHTML, innerText
  • 针对 input element 常有的 value,也支持了
  • 针对 className, 也支持了
  • Function 等使用 页面环境 - inject.js / content.js (视乎 @inject-into 有否指定了 content)
  • 设计上兼容了在 content环境 (@inject-into content) 执行 ,即 自己呼叫自己

测试环境

  • github.com (CSP)
  • content-security-policy.com (CSP)
  • youtube.com (TTP)
Screenshot 2026-02-12 at 12 23 40

Test

// ==UserScript==
// @name         GM_addElement test
// @match        *://*/*?test_GM_addElement
// @grant        GM_addElement
// @version      0
// ==/UserScript==

/*
### Example Sites
* https://content-security-policy.com/?test_GM_addElement (CSP)
* https://github.com/scriptscat/scriptcat/?test_GM_addElement (CSP)
* https://www.youtube.com/account_playback/?test_GM_addElement (TTP)
*/

const logSection = (title) => {
    console.log(`\n=== ${title} ===`);
};

const logStep = (message, data) => {
    if (data !== undefined) {
        console.log(`→ ${message}:`, data);
    } else {
        console.log(`→ ${message}`);
    }
};


// ─────────────────────────────────────────────
// Native textarea insertion
// ─────────────────────────────────────────────
logSection("Native textarea insertion - BEGIN");

const textarea = GM_addElement('textarea', {
    native: true,
    value: "myText",
});

logStep("Textarea value", textarea.value);
logSection("Native textarea insertion - END");


// ─────────────────────────────────────────────
// Div insertion
// ─────────────────────────────────────────────
logSection("Div insertion - BEGIN");

GM_addElement('div', {
    innerHTML: '<div id="test777"></div>',
});

logSection("Div insertion - END");


// ─────────────────────────────────────────────
// Span insertion
// ─────────────────────────────────────────────
logSection("Span insertion - BEGIN");

GM_addElement(document.getElementById("test777"), 'span', {
    className: "test777-span",
    textContent: 'Hello World!',
});

logStep(
    "Span content",
    document.querySelector("span.test777-span").textContent
);

logSection("Span insertion - END");


// ─────────────────────────────────────────────
// Image insertion
// ─────────────────────────────────────────────
logSection("Image insertion - BEGIN");

let img;
await new Promise((resolve, reject) => {
    img = GM_addElement(document.body, 'img', {
        src: 'https://www.tampermonkey.net/favicon.ico',
        onload: resolve,
        onerror: reject
    });

    logStep("Image element inserted");
});

logStep("Image loaded");
logSection("Image insertion - END");


// ─────────────────────────────────────────────
// Script insertion
// ─────────────────────────────────────────────
logSection("Script insertion - BEGIN");

GM_addElement(document.body, 'script', {
    textContent: "window.myCustomFlag = true; console.log('script run ok');",
}, img);

logStep(
    "Script inserted before image",
    img.previousSibling?.nodeName === "SCRIPT"
);

logSection("Script insertion - END");

@cyfung1031 cyfung1031 changed the title 配合 1.3 scripting, 重构 GM_addElement [v1.3] 配合 1.3 scripting, 重构 GM_addElement (bug 修补 + 功能改进) Feb 12, 2026
@cyfung1031 cyfung1031 linked an issue Feb 12, 2026 that may be closed by this pull request
@cyfung1031 cyfung1031 added enhancement New feature or request hotfix 需要尽快更新到扩展商店 labels Feb 12, 2026
@cyfung1031 cyfung1031 marked this pull request as ready for review February 12, 2026 10:41
@CodFrm
Copy link
Member

CodFrm commented Feb 12, 2026

1.3 scripting - scripting.js 不能在CSP插入元素

我看没问题啊

@cyfung1031

This comment was marked as outdated.

@cyfung1031
Copy link
Collaborator Author

cyfung1031 commented Feb 12, 2026

1.3 scripting - scripting.js 不能在CSP插入元素

我看没问题啊

呀。我打錯&記錯了
是TTP

// ==UserScript==
// @name         New Userscript MWNB-1
// @namespace    https://docs.scriptcat.org/
// @version      0.1.0
// @description  try to take over the world!
// @author       You
// @match        https://scriptcat.org/zh-CN?_TTP_GM_addElement
// @grant        GM_addElement
// @noframes
// ==/UserScript==

(function () {
  'use strict';

  trustedTypes.createPolicy('abc', {
    createHTML: (string) => { throw new Error('Unsafe HTML') },
    createScript: (string) => { throw new Error('Unsafe script') },
    createScriptURL: (string) => { throw new Error('Unsafe URL') }
  });

  console.log("START");
  const elm = GM_addElement("script", { textContent: "console.log('userscript running')" });
  console.log("END", elm);

  // 成功的话会打印 userscript running
})();

未修好的 v1.3

Screenshot 2026-02-12 at 19 59 02

PR修正后

Screenshot 2026-02-12 at 20 00 17

@cyfung1031 cyfung1031 added the P1 🔥 重要但是不紧急的内容 label Feb 15, 2026
@CodFrm CodFrm requested a review from Copilot February 15, 2026 08:02
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

这个 PR 重构了 GM_addElement API 以解决 v1.3 scripting 中的 CSP (Content Security Policy) 和 TTP (Trusted Types Policy) 限制问题。主要改进包括:

Changes:

  • 移除了 scripting.ts 中通过消息传递处理 GM_addElement 的旧实现
  • 在 gm_api.ts 中实现了新的 GM_addElement,直接在 content 环境处理 DOM 操作以绕过 CSP 限制
  • 添加了 native 选项支持在页面环境创建元素(用于 Custom Elements)
  • 新增第四个参数 refNode 支持 insertBefore 功能
  • 扩展了属性支持(innerHTML, innerText, outerHTML, className, value)
  • 在 custom_event_message.ts 中添加了新的消息处理逻辑
  • 添加了 dispatchMyEvent 辅助函数简化事件分发

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 15 comments.

Show a summary per file
File Description
src/app/service/content/scripting.ts 移除了旧的 GM_addElement 消息处理代码(38行)
src/app/service/content/gm_api/gm_api.ts 重写 GM_addElement 实现,新增 120 行代码支持 CSP/TTP 绕过、native 模式和 insertBefore 功能
src/app/service/content/global.ts 在 Native 对象中添加 createElement 和 ownFragment 以防止页面篡改
packages/message/custom_event_message.ts 添加消息处理逻辑以支持在 content 环境创建和插入元素
packages/message/common.ts 新增 dispatchMyEvent 辅助函数和相关类型定义
example/tests/gm_add_element.js 添加测试脚本验证新功能(native、insertBefore、各种属性)
Comments suppressed due to low confidence (1)

src/app/service/content/gm_api/gm_api.ts:889

  • GM.addElement 的 Promise 版本缺少第四个参数 refNode 的支持。这导致 GM.addElement 无法使用新增的 insertBefore 功能。建议更新签名以支持完整的参数列表,或者至少在文档中说明 GM.addElement 不支持此功能。
  @GMContext.API({ depend: ["GM_addElement"] })
  public "GM.addElement"(
    parentNode: Node | string,
    tagName: string | Record<string, string | number | boolean>,
    attrs: Record<string, string | number | boolean> = {}
  ): Promise<Element | undefined> {
    return new Promise<Element | undefined>((resolve) => {
      const ret = this.GM_addElement(parentNode, tagName, attrs);
      resolve(ret);
    });
  }

Comment on lines +795 to +796
// 最小值为 1000000000 避免与其他 related Id 操作冲突
let randInt = Math.floor(Math.random() * 1147483647 + 1000000000); // 32-bit signed int
Copy link

Copilot AI Feb 15, 2026

Choose a reason for hiding this comment

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

代码注释中提到 "32-bit signed int",但 JavaScript 的 Number 类型是 64-bit 浮点数。Math.random() * 1147483647 的结果可以精确表示,但注释可能会误导读者。建议澄清这是为了避免溢出还是其他原因。

Suggested change
// 最小值为 1000000000 避免与其他 related Id 操作冲突
let randInt = Math.floor(Math.random() * 1147483647 + 1000000000); // 32-bit signed int
// 在 10^9 ~ 2.1×10^9 区间内生成一次性随机 ID,用于与其他 related Id 的数值空间错开
let randInt = Math.floor(Math.random() * 1147483647 + 1000000000);

Copilot uses AI. Check for mistakes.
Comment on lines +804 to +807
// 目前未有直接取得 eventFlag 的方法。通过 page/content 的 receiveFlag 反推 eventFlag
const eventFlag = (this.message as CustomEventMessage).receiveFlag
.split(`${DefinedFlags.outboundFlag}${DefinedFlags.domEvent}`)[0]
.slice(0, -2);
Copy link

Copilot AI Feb 15, 2026

Choose a reason for hiding this comment

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

通过字符串操作(split 和 slice)从 receiveFlag 反推 eventFlag 是脆弱的实现。如果 DefinedFlags 的格式发生变化,这段代码会静默失败。建议提供一个明确的方法来获取 eventFlag,或者在 CustomEventMessage 中存储 eventFlag 以便直接访问。

Copilot uses AI. Check for mistakes.
Comment on lines 760 to 765
public GM_addElement(
parentNode: Node | string,
tagName: string | Record<string, string | number | boolean>,
attrs: Record<string, string | number | boolean> = {}
attrs: Record<string, string | number | boolean> | Node | null = {},
refNode: Node | null = null
): Element | undefined {
Copy link

Copilot AI Feb 15, 2026

Choose a reason for hiding this comment

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

TypeScript 类型定义需要更新以支持新的 API 签名。当前的类型定义缺少:1) 第四个参数 refNode 用于 insertBefore 功能;2) attrs 中的 native 选项;3) 新支持的属性如 innerHTML、innerText、outerHTML、className、value。建议更新 src/types/scriptcat.d.ts 文件。

Copilot uses AI. Check for mistakes.
}
return (<CustomEventMessage>this.message).getAndDelRelatedTarget(resp.data) as Element;

refNode = refNode instanceof Node && refNode.parentNode === sParentNode ? refNode : null;
Copy link

Copilot AI Feb 15, 2026

Choose a reason for hiding this comment

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

refNode 验证逻辑(refNode.parentNode === sParentNode)会在 refNode 还未插入到 DOM 时将其设为 null。这限制了 API 的灵活性,因为用户可能想在元素插入前就指定插入位置。建议移除此验证,让 insertBefore 在 refNode 不在 parent 中时自然失败,或者提供更清晰的错误消息。

Suggested change
refNode = refNode instanceof Node && refNode.parentNode === sParentNode ? refNode : null;
// 仅在 refNode 为合法 DOM 节点时保留,其是否属于 sParentNode 交由后续 DOM 操作自行校验
refNode = refNode instanceof Node ? refNode : null;

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

暂不考虑。日后再算

}
if (resFalse !== false && eventInitDict.cancelable === true) {
// 通讯设置正确的话应不会发生
throw new Error("Page Message Error");
Copy link

Copilot AI Feb 15, 2026

Choose a reason for hiding this comment

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

dispatchMyEvent 函数对于失败的情况(resFalse !== false && cancelable === true)会抛出通用错误 "Page Message Error"。这个错误消息不够具体,无法帮助开发者诊断问题。建议提供更详细的错误信息,包括 event type 和可能的失败原因。

Suggested change
throw new Error("Page Message Error");
let eventInitDebug = "";
try {
eventInitDebug = JSON.stringify(eventInitDict);
} catch {
eventInitDebug = "[unserializable eventInitDict]";
}
throw new Error(
`Page Message Error: dispatchMyEvent expected event "${type}" (cancelable: true) to be canceled, but dispatch returned ${String(
resFalse
)}. eventInitDict: ${eventInitDebug}`
);

Copilot uses AI. Check for mistakes.
Comment on lines +794 to +797
// 不需要 incremental. 这个值只是在用来作一次性同步处理
// 最小值为 1000000000 避免与其他 related Id 操作冲突
let randInt = Math.floor(Math.random() * 1147483647 + 1000000000); // 32-bit signed int
randInt -= randInt % 100; // 用此方法可以生成不重复的 id
Copy link

Copilot AI Feb 15, 2026

Choose a reason for hiding this comment

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

随机 ID 生成存在潜在的冲突风险。虽然使用了大范围(1000000000 到 2147483647)并取整到 100 的倍数,但在高并发场景下仍可能产生冲突。建议使用递增的 ID 生成器或添加冲突检测机制。

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

只是一次性用途。不需要避免冲突

Comment on lines +95 to +98
export const dispatchMyEvent = <T extends Record<string, TNestedPrimitive>>(
type: string,
eventInitDict: MouseEventInit | Omit<T, "movementX" | "relatedTarget">
) => {
Copy link

Copilot AI Feb 15, 2026

Choose a reason for hiding this comment

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

dispatchMyEvent 的类型定义不够严格。Omit<T, "movementX" | "relatedTarget"> 允许 eventInitDict 包含这些字段,但实际检查是使用 "in" 操作符。这可能导致类型安全问题。建议使用更严格的类型约束,或者在运行时添加更明确的验证。

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

typescript 要用 in 呀

}
if (!el) {
// 一般情况(非 isNative) 或 元素生成失败 (报错或回传null/undefined)
const frag = Native.ownFragment;
Copy link

Copilot AI Feb 15, 2026

Choose a reason for hiding this comment

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

Native.ownFragment 使用单例 DocumentFragment,这在多次调用 GM_addElement 时会导致问题。当一个元素被 appendChild 到 fragment 后,后续调用会共享同一个 fragment,可能导致元素被意外移除或覆盖。应该为每次调用创建新的 DocumentFragment 实例。

Suggested change
const frag = Native.ownFragment;
const frag = document.createDocumentFragment();

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

同步操作。不会有这情况

Comment on lines +820 to +826
} catch {
// 避免元素生成失败时无法执行。此情况应 fallback
console.warn("GM API: Native.createElement failed");
}
}
if (!el) {
// 一般情况(非 isNative) 或 元素生成失败 (报错或回传null/undefined)
Copy link

Copilot AI Feb 15, 2026

Choose a reason for hiding this comment

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

在 isNative 模式下,如果 Native.createElement 失败(抛出异常或返回 null/undefined),代码会 fallback 到 content 环境创建元素。但这可能违背用户的预期 - 如果用户明确指定了 native: true,可能是因为需要在页面环境创建特殊元素(如 Custom Elements)。Fallback 到 content 环境可能导致功能异常。建议在 isNative 失败时直接抛出错误,或至少记录更明确的警告信息。

Suggested change
} catch {
// 避免元素生成失败时无法执行。此情况应 fallback
console.warn("GM API: Native.createElement failed");
}
}
if (!el) {
// 一般情况(非 isNative) 或 元素生成失败 (报错或回传null/undefined)
} catch (err) {
// 在 native 模式下元素创建失败时,不应静默 fallback 到 content,以免违背用户预期
console.error("GM API: Native.createElement failed in native mode", err);
throw new Error("GM API: Native.createElement failed in native mode");
}
// Native.createElement 未抛异常但返回了 null/undefined,同样视为 native 模式下的致命错误
if (!el) {
console.error("GM API: Native.createElement returned no element in native mode");
throw new Error("GM API: Native.createElement returned no element in native mode");
}
} else {
// 一般情况(非 isNative) 使用 content 环境创建元素

Copilot uses AI. Check for mistakes.
Copy link
Member

@CodFrm CodFrm left a comment

Choose a reason for hiding this comment

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

🔍 PR #1233 代码审查:重构 GM_addElement

📋 概述

此 PR 对 GM_addElement API 进行了重大重构,主要目的是:

  1. 修复 CSP/TTP 限制下无法插入元素的 bug — 统一由 content.js 处理元素插入
  2. 新增功能 — 支持 native: true 原生创建元素、insertBefore 式的 refNode 参数、更多属性如 innerHTML/outerHTML/innerText/className/value
  3. 移除旧逻辑 — 删除 scripting.tsGM_addElement 的 case 分支,不再走 background 消息中转

✅ 优点

  • 架构改进明显:旧实现通过 syncSendMessage 经 scripting 中转,新实现直接在 inject/content 端利用 dispatchMyEvent 同步事件通讯完成元素创建和插入,更加直接高效
  • 更好的 CSP/TTP 兼容性:文字/数字类属性在 content.js 设置以避开 TrustedTypes 限制
  • 功能扩展合理native: truerefNode、更多属性支持等都是实际需求驱动
  • 注释详尽:代码中有充分的中文注释解释设计意图
  • 测试脚本完备:新增了 example/tests/gm_add_element.js 覆盖多种使用场景

⚠️ 问题和建议

1. 🔴 GM_addStyle 仍使用旧的 syncSendMessage 路径,而 scripting.ts 已删除 GM_addElement 的 case

GM_addStylegm_api.ts 第 724–748 行)仍然通过 syncSendMessage 调用 scripting.ts 中的 GM_addElement case,但该 case 已在此 PR 中被删除。这将导致 GM_addStyle 完全失效!

// gm_api.ts — GM_addStyle 仍旧使用已删除的路径
const resp = (<CustomEventMessage>this.message).syncSendMessage({
  action: `${this.prefix}/runtime/gmApi`,
  data: {
    uuid: this.scriptRes.uuid,
    api: "GM_addElement",  // ← scripting.ts 中此 case 已被删除
    params: [null, "style", { textContent: css }, isContent],
  },
});

建议:将 GM_addStyle 也重构为使用新的 GM_addElement 路径,例如直接调用 this.GM_addElement('style', { textContent: css })

2. 🔴 随机 ID 碰撞风险

let randInt = Math.floor(Math.random() * 1147483647 + 1000000000);
randInt -= randInt % 100; // 用此方法可以生成不重复的 id

注释说"可以生成不重复的 id",但 Math.random() 并不保证唯一性。如果两个脚本或同一脚本快速连续调用 GM_addElement,理论上可能产生相同的 randInt,导致 relatedTargetMap 中的数据被覆盖。

建议:使用递增计数器(类似现有的 relateId)或在现有 relateId 基础上扩展,确保 ID 唯一性。例如:

// 在类或模块级别维护一个递增计数器
const baseId = (relateId = relateId + 100); // 每次递增 100,留出 id0~id3 的空间

3. 🟡 eventFlag 的反推方式十分脆弱

const eventFlag = (this.message as CustomEventMessage).receiveFlag
  .split(`${DefinedFlags.outboundFlag}${DefinedFlags.domEvent}`)[0]
  .slice(0, -2);

通过字符串分割和截取来反推 eventFlag 依赖于 flag 的内部格式,如果 DefinedFlags 的格式发生变化,此处会无声地出错。PR 描述中也提到"目前未有直接取得 eventFlag 的方法"。

建议:在 CustomEventMessage 类中暴露一个 getter 方法(如 getEventFlag()),或将 eventFlag 作为构造参数保存,避免外部依赖内部格式。

4. 🟡 Native.ownFragment 作为单例的线程安全问题

// global.ts
ownFragment: new DocumentFragment(),

Native.ownFragment 是一个全局共享的 DocumentFragment。如果在极端情况下(如事件监听器中触发了另一次 GM_addElement 调用),frag.lastChild 可能返回错误的元素。

建议:每次调用时创建新的 DocumentFragment,或者在使用后立即清空:

const frag = new DocumentFragment(); // 每次新建

5. 🟡 dispatchMyEventcancelable 检查可能导致误报错误

if (resFalse !== false && eventInitDict.cancelable === true) {
  throw new Error("Page Message Error");
}

如果 content 端的事件监听器尚未准备好(例如时序问题),preventDefault() 不会被调用,会导致抛出异常。注释说"通讯设置正确的话应不会发生",但在实际环境中初始化时序可能出问题。

建议:考虑添加 fallback 或更友好的错误提示,而不是直接 throw。或者增加重试机制。

6. 🟡 custom_event_message.tsappendOrInsert 处理缺少错误检查

const el = <Element>this.getAndDelRelatedTarget(id1);
const parent = <Node>this.getAndDelRelatedTarget(id2);

如果 getAndDelRelatedTarget 返回 undefined(例如 ID 碰撞导致数据被提前消费),后续的 el.setAttributeparent.appendChild 会抛出不明确的运行时错误。

建议:添加空值检查并提供有意义的错误信息:

if (!el || !parent) throw new Error("GM_addElement: relatedTarget not found");

7. 🟡 innerHTML/outerHTML 的安全风险

在 content 环境中直接设置 innerHTML/outerHTML 可能绕过页面的 CSP 或 TrustedTypes 策略(这正是此 PR 的目的之一),但也意味着用户脚本可以注入任意 HTML。虽然用户脚本本身就有高权限,但仍需注意这可能被恶意脚本利用。

建议:在文档中明确说明此行为的安全影响,确保用户理解权限范围。

8. 🟢 小问题:dispatchMyEvent 函数命名

dispatchMyEvent 名称不够描述性,不太符合项目中其他函数的命名风格(如 pageDispatchEventpageDispatchCustomEvent)。

建议:考虑更具描述性的名称,如 dispatchBridgeEventdispatchRelatedTargetEvent

9. 🟢 类型标注可以更精确

attrs: Record<string, string | number | boolean> | Node | null = {}

attrs 参数的类型联合了 Record<...> | Node | null,使得函数签名较为混乱。这是因为 GM_addElement 支持两种调用模式(有/无 parentNode),建议使用函数重载让类型更清晰。


📊 总结

维度 评价
代码正确性 ⚠️ GM_addStyle 路径断裂是严重 bug
架构设计 ✅ 方向正确,但 eventFlag 反推和 ID 生成需改进
安全性 🟡 innerHTML/outerHTML 需注意,但在用户脚本场景下可接受
测试覆盖 ✅ 有手动测试脚本,但缺少单元测试
代码风格 🟡 函数命名可改进,PR 作者自己也提到风格可能需重构

总体评价:功能设计思路好,解决了实际问题(CSP/TTP 绕过),但 GM_addStyle 的回归 bug 必须在合并前修复,且 ID 唯一性和 eventFlag 反推方式需要加固。建议修复关键问题后再合并。

🤖 Generated with Claude Code

@cyfung1031
Copy link
Collaborator Author

确实写得有点随便
不过如果你可以接受不需重构的话,可以照AI意见改一下 (虽然不是全都对)

@CodFrm
Copy link
Member

CodFrm commented Feb 16, 2026

确实写得有点随便

不过如果你可以接受不需重构的话,可以照AI意见改一下 (虽然不是全都对)

简单看了一下,那确实还是得整理一下,我这两天处理

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request hotfix 需要尽快更新到扩展商店 P1 🔥 重要但是不紧急的内容

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] GM_addElement Bug

2 participants