Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
106 changes: 106 additions & 0 deletions example/tests/gm_add_element.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// ==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");
26 changes: 24 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 Expand Up @@ -85,3 +85,25 @@ export const createMouseEvent =
: (type: string, eventInitDict?: MouseEventInit | undefined): MouseEvent => {
return new MouseEventClone(type, eventInitDict);
};

type TPrimitive = string | number | boolean;
interface INestedPrimitive {
[key: string]: TPrimitive | INestedPrimitive;
}
type TNestedPrimitive = TPrimitive | INestedPrimitive;

export const dispatchMyEvent = <T extends Record<string, TNestedPrimitive>>(
type: string,
eventInitDict: MouseEventInit | Omit<T, "movementX" | "relatedTarget">
) => {
Comment on lines +95 to +98
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 呀

let resFalse;
if ("movementX" in eventInitDict) {
resFalse = pageDispatchEvent(createMouseEvent(type, eventInitDict));
} else {
resFalse = pageDispatchCustomEvent(type, eventInitDict);
}
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.
}
};
26 changes: 25 additions & 1 deletion packages/message/custom_event_message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,34 @@ export class CustomEventMessage implements Message {
this.receiveFlag = `${messageFlag}${isInbound ? DefinedFlags.inboundFlag : DefinedFlags.outboundFlag}${DefinedFlags.domEvent}`;
this.sendFlag = `${messageFlag}${isInbound ? DefinedFlags.outboundFlag : DefinedFlags.inboundFlag}${DefinedFlags.domEvent}`;
pageAddEventListener(this.receiveFlag, (event: Event) => {
if (event instanceof MouseEventClone && event.movementX === 0 && event.cancelable) {
if (event instanceof CustomEventClone && event.detail?.appendOrInsert === true) {
const id1 = event.detail?.id1 as number;
const id2 = event.detail?.id2 as number;
const id3 = event.detail?.id3 as number | undefined | null;
const el = <Element>this.getAndDelRelatedTarget(id1);
const parent = <Node>this.getAndDelRelatedTarget(id2);
const refNode = id3 ? <Node>this.getAndDelRelatedTarget(id3) : null;
const attrs = (event.detail?.attrs ?? {}) as Record<string, string | number>;
const props = new Set(["textContent", "innerHTML", "innerText", "outerHTML", "className", "value"] as const);
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.

属性设置逻辑中的 props Set 包含了 "outerHTML",但设置 outerHTML 会替换整个元素,这可能导致刚创建的元素被替换掉,后续的 appendChild/insertBefore 操作会失败。建议从 props 中移除 "outerHTML",或者在文档中明确说明此行为。

Suggested change
const props = new Set(["textContent", "innerHTML", "innerText", "outerHTML", "className", "value"] as const);
const props = new Set(["textContent", "innerHTML", "innerText", "className", "value"] as const);

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.

改了 outerHTML 还是用一个element呀

for (const [key, value] of Object.entries(attrs)) {
if (props.has(key as any)) (el as any)[key] = value;
else el.setAttribute(key, value as string);
}
refNode ? parent.insertBefore(el, refNode) : parent.appendChild(el);
event.preventDefault();
} else if (event instanceof CustomEventClone && typeof event.detail?.createElement === "string") {
const id0 = event.detail?.id0 as number;
const frag = <DocumentFragment>this.getAndDelRelatedTarget(id0);
if (!(frag instanceof DocumentFragment)) {
throw new Error("Unexpected Error in createElement");
}
frag.appendChild(document.createElement(event.detail.createElement as string));
event.preventDefault();
} else if (event instanceof MouseEventClone && event.movementX === 0 && event.cancelable) {
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/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ export const Native = {
structuredClone: typeof structuredClone === "function" ? structuredClone : unsupportedAPI,
jsonStringify: JSON.stringify.bind(JSON),
jsonParse: JSON.parse.bind(JSON),
createElement: Document.prototype.createElement,
ownFragment: new DocumentFragment(),
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.

使用单例 DocumentFragment(ownFragment)会导致并发问题。当多个 GM_addElement 调用同时执行时,它们会共享同一个 fragment,导致元素互相覆盖。应该移除这个单例,改为在需要时创建新的 DocumentFragment 实例。

Suggested change
ownFragment: new DocumentFragment(),
get ownFragment() {
return new DocumentFragment();
},

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.

同步操作。不会有这情况

} as const;

export const customClone = (o: any) => {
Expand Down
131 changes: 110 additions & 21 deletions src/app/service/content/gm_api/gm_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ import GMContext from "./gm_context";
import { type ScriptRunResource } from "@App/app/repo/scripts";
import type { ValueUpdateDataEncoded } from "../types";
import { connect, sendMessage } from "@Packages/message/client";
import { isContent } from "@Packages/message/common";
import { dispatchMyEvent, isContent } from "@Packages/message/common";
import { getStorageName } from "@App/pkg/utils/utils";
import { ListenerManager } from "../listener_manager";
import { decodeRValue, encodeRValue, type REncoded } from "@App/pkg/utils/message_value";
import { type TGMKeyValue } from "@App/app/repo/value";
import type { ContextType } from "./gm_xhr";
import { convObjectToURL, GM_xmlhttpRequest, toBlobURL, urlToDocumentInContentPage } from "./gm_xhr";
import { DefinedFlags } from "../../service_worker/runtime.consts";
import { ScriptEnvTag } from "@Packages/message/consts";

// 内部函数呼叫定义
export interface IGM_Base {
Expand Down Expand Up @@ -758,44 +760,131 @@ export default class GMApi extends GM_Base {
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 {
Comment on lines 760 to 765
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.
if (!this.message || !this.scriptRes) return;
// 与content页的消息通讯实际是同步,此方法不需要经过background
// 这里直接使用同步的方式去处理, 不要有promise
let parentNodeId: number | null;
// 在content脚本执行的话,与直接 DOM 无异
// TrustedTypes 限制了对 DOM 的 innerHTML/outerHTML 的操作 (TrustedHTML)
// TrustedTypes 限制了对 script 的 innerHTML/outerHTML/textContent/innerText 的操作 (TrustedScript)
// CSP 限制了对 appendChild/insertChild/replaceChild/insertAdjacentElement ... 等DOM插入移除操作

// let parentNodeId: number | null;
let sParentNode: Node | null = null;
if (typeof parentNode !== "string") {
const id = (<CustomEventMessage>this.message).sendRelatedTarget(parentNode);
parentNodeId = id;
sParentNode = parentNode as Node;
attrs = (attrs || {}) as Record<string, string | number | boolean>;
} else {
parentNodeId = null;
refNode = attrs as Node | null;
attrs = (tagName || {}) as Record<string, string | number | boolean>;
tagName = parentNode as string;
}
if (typeof tagName !== "string") throw new Error("The parameter 'tagName' of GM_addElement shall be a string.");
if (typeof attrs !== "object") throw new Error("The parameter 'attrs' of GM_addElement shall be an object.");
const resp = (<CustomEventMessage>this.message).syncSendMessage({
action: `${this.prefix}/runtime/gmApi`,
data: {
uuid: this.scriptRes.uuid,
api: "GM_addElement",
params: [parentNodeId, tagName, attrs, isContent],
},
});
if (resp.code) {
throw new Error(resp.message);

// 决定 parentNode
if (!sParentNode) {
sParentNode = document.head || document.body || document.documentElement || document.querySelector("*");
// MV3 应该都至少有一个元素 (document.documentElement), 这个错误应该不会发生
if (!sParentNode) throw new Error("Page Element Error");
}
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.

暂不考虑。日后再算


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

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


const id0 = randInt;
const id1 = randInt + 1;
const id2 = randInt + 2;
let id3;

// 目前未有直接取得 eventFlag 的方法。通过 page/content 的 receiveFlag 反推 eventFlag
const eventFlag = (this.message as CustomEventMessage).receiveFlag
.split(`${DefinedFlags.outboundFlag}${DefinedFlags.domEvent}`)[0]
.slice(0, -2);
Comment on lines +804 to +807
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.

// content 的 receiveFlag
const ctReceiveFlag = `${eventFlag}${ScriptEnvTag.content}${DefinedFlags.outboundFlag}${DefinedFlags.domEvent}`;

let el;

const isNative = attrs.native === true;
if (isNative) {
// 直接使用页面的元素生成方法。某些情况例如 Custom Elements 用户可能需要直接在页面环境生成元素
// CSP 或 TrustedTypes 目前未有对 document.createElement 做出任何限制。
try {
el = <Element>Native.createElement.call(document, tagName as string);
} catch {
// 避免元素生成失败时无法执行。此情况应 fallback
console.warn("GM API: Native.createElement failed");
}
}
if (!el) {
// 一般情况(非 isNative) 或 元素生成失败 (报错或回传null/undefined)
Comment on lines +820 to +826
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.
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.

同步操作。不会有这情况

// 设置 fragment
dispatchMyEvent(ctReceiveFlag, { cancelable: true, movementX: id0, relatedTarget: frag });
// 执行 createElement 并放入 fragment
dispatchMyEvent(ctReceiveFlag, { cancelable: true, createElement: `${tagName}`, id0: id0 });
// 从 fragment 取回新增的 Element
el = frag.lastChild as Element | null;
// 如特殊情况导致无法创建元素,则报错。
if (!el) throw new Error("GM API: createElement failed");
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.

在元素创建失败时抛出的错误消息 "GM API: createElement failed" 不够具体。建议包含更多上下文信息,例如标签名称和失败原因,以便用户调试。

Copilot uses AI. Check for mistakes.
}

// 控制传送参数,避免参数出现 non-json-selizable
const attrsCT = {} as Record<string, string | number>;
for (const [key, value] of Object.entries(attrs)) {
if (key === "native") continue;
if (typeof value === "string" || typeof value === "number") {
// 数字不是标准的 attribute value type, 但常见于实际使用
attrsCT[key] = value;
} else {
// property setter for non attribute (e.g. Function, Symbol, boolean, etc)
// Function, Symbol 无法跨环境
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.

属性处理逻辑存在安全风险:对于非字符串/数字的值(如 Function、Symbol、boolean 等),代码直接设置为元素的属性(line 848: (el as any)[key] = value)。这可能导致原型污染或其他安全问题。建议添加白名单验证,只允许已知安全的属性名称。

Suggested change
// Function, Symbol 无法跨环境
// Function, Symbol 无法跨环境
// 为避免原型污染等风险,这里显式跳过若干危险属性名
if (key === "__proto__" || key === "prototype" || key === "constructor") {
// 记录一条警告,方便开发者排查潜在问题
console.warn("GM API: unsafe property key ignored on element:", key);
continue;
}

Copilot uses AI. Check for mistakes.
(el as any)[key] = value;
}
}

// 设置 id1 -> el
dispatchMyEvent(ctReceiveFlag, { cancelable: true, movementX: id1, relatedTarget: el });

// 设置 id2 -> parentNode
dispatchMyEvent(ctReceiveFlag, { cancelable: true, movementX: id2, relatedTarget: sParentNode });

// 执行 attrsCT 设置并 appendChild

if (refNode) {
id3 = randInt + 3;
// 设置 id3 -> refNode
dispatchMyEvent(ctReceiveFlag, { cancelable: true, movementX: id3, relatedTarget: refNode });
}

dispatchMyEvent(ctReceiveFlag, {
cancelable: true,
appendOrInsert: true,
id1: id1,
id2: id2,
id3: id3,
attrs: attrsCT,
});

// 回传元素
return el;
}

@GMContext.API({ depend: ["GM_addElement"] })
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
): Promise<Element | undefined> {
return new Promise<Element | undefined>((resolve) => {
const ret = this.GM_addElement(parentNode, tagName, attrs);
const ret = this.GM_addElement(parentNode, tagName, attrs, refNode);
resolve(ret);
});
}
Expand Down
33 changes: 0 additions & 33 deletions src/app/service/content/scripting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,39 +95,6 @@ export default class ScriptingRuntime {
xhr.send();
});
}
case "GM_addElement": {
const [parentNodeId, tagName, tmpAttr, isContent] = data.params;

// 根据来源选择不同的消息桥(content / inject)
const msg = isContent ? this.senderToContent : this.senderToInject;

// 取回 parentNode(如果存在)
let parentNode: Node | undefined;
if (parentNodeId) {
parentNode = msg.getAndDelRelatedTarget(parentNodeId) as Node | undefined;
}

// 创建元素并设置属性
const el = <Element>document.createElement(tagName);
const attr = tmpAttr ? { ...tmpAttr } : {};
let textContent = "";
if (attr.textContent) {
textContent = attr.textContent;
delete attr.textContent;
}
for (const key of Object.keys(attr)) {
el.setAttribute(key, attr[key]);
}
if (textContent) el.textContent = textContent;

// 优先挂到 parentNode,否则挂到 head/body/任意节点
const node = parentNode || document.head || document.body || document.querySelector("*");
node.appendChild(el);

// 返回节点引用 id,供另一侧再取回
const nodeId = msg.sendRelatedTarget(el);
return nodeId;
}
case "GM_log":
// 拦截 GM_log:直接打印到控制台(某些页面可能劫持 console.log)
switch (data.params.length) {
Expand Down
Loading