Skip to content

Commit

Permalink
✨ 支持GM_addElement #102
Browse files Browse the repository at this point in the history
  • Loading branch information
CodFrm committed Dec 2, 2022
1 parent 0ff6ddd commit 0d0f4db
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 14 deletions.
15 changes: 15 additions & 0 deletions example/gm_add_element.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// ==UserScript==
// @name gm add element
// @namespace https://bbs.tampermonkey.net.cn/
// @version 0.1.0
// @description 在页面中插入元素,可以绕过CSP限制
// @author You
// @match https://github.com/scriptscat/scriptcat
// @grant GM_addElement
// ==/UserScript==

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"
});

console.log(el);
40 changes: 40 additions & 0 deletions src/app/message/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,23 @@ export default class MessageContent

channelManager: ChannelManager;

relatedTarget: Map<number, Element>;

constructor(eventId: string, isContent: boolean) {
super();
this.eventId = eventId;
this.isContent = isContent;
this.channelManager = new WarpChannelManager((data) => {
this.nativeSend(data);
});
this.relatedTarget = new Map<number, Element>();
document.addEventListener(
(isContent ? "ct" : "fd") + eventId,
(event: unknown) => {
if (event instanceof MouseEvent) {
this.relatedTarget.set(event.detail, <Element>event.relatedTarget);
return;
}
const message = (<
{
detail: {
Expand Down Expand Up @@ -75,6 +82,24 @@ export default class MessageContent
return channel.syncSend(action, data);
}

// content与inject通讯为阻塞可以实现真同步,使用回调的方式返回参数
sendCallback(action: string, data: any, callback: (resp: any) => void) {
const channel = this.channelManager.channel();
channel.handler = callback;
this.nativeSend({
action,
data,
stream: channel.flag,
channel: false,
});
}

getAndDelRelatedTarget(id: number) {
const target = this.relatedTarget.get(id);
this.relatedTarget.delete(id);
return target;
}

nativeSend(data: any): void {
let detail = data;
if (typeof cloneInto !== "undefined") {
Expand All @@ -86,6 +111,21 @@ export default class MessageContent
console.log(e);
}
}

// 特殊处理relatedTarget
if (typeof detail.data.relatedTarget === "object") {
// 先将relatedTarget转换成id发送过去
const target = detail.data.relatedTarget;
delete detail.data.relatedTarget;
detail.data.relatedTarget = Math.ceil(Math.random() * 1000000);
// 可以使用此种方式交互element
const ev = new MouseEvent((this.isContent ? "fd" : "ct") + this.eventId, {
detail: detail.data.relatedTarget,
relatedTarget: target,
});
document.dispatchEvent(ev);
}

const ev = new CustomEvent((this.isContent ? "fd" : "ct") + this.eventId, {
detail,
});
Expand Down
39 changes: 28 additions & 11 deletions src/app/message/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { v4 as uuidv4 } from "uuid";
import LoggerCore from "../logger/core";
import { Channel } from "./channel";
import MessageContent from "./content";

export type MessageSender = {
tabId?: number;
Expand Down Expand Up @@ -141,19 +142,26 @@ export abstract class MessageHander {
if (handler) {
const ret = handler(message.action!, message.data, sender);
if (ret) {
ret
.then((data) => {
channelManager.nativeSend({
stream: message.stream,
data,
});
})
.catch((err) => {
channelManager.nativeSend({
error: err.message,
stream: message.stream,
if (ret instanceof Promise) {
ret
.then((data) => {
channelManager.nativeSend({
stream: message.stream,
data,
});
})
.catch((err) => {
channelManager.nativeSend({
error: err.message,
stream: message.stream,
});
});
} else {
channelManager.nativeSend({
stream: message.stream,
data: ret,
});
}
} else {
LoggerCore.getLogger({ component: "message" }).warn(
"handler return is null"
Expand Down Expand Up @@ -239,4 +247,13 @@ export class ProxyMessageManager implements MessageManager {
});
this.channelMap.clear();
}

// content与inject通讯录可以实现真同步,使用回调的方式返回参数
sendCallback(action: string, data: any, callback: (resp: any) => void) {
(<MessageContent>this.manager).sendCallback(action, data, callback);
}

getAndDelRelatedTarget(id: number) {
return (<MessageContent>this.manager).getAndDelRelatedTarget(id);
}
}
39 changes: 39 additions & 0 deletions src/runtime/content/content.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ExternalMessage } from "@App/app/const";
import MessageContent from "@App/app/message/content";
import MessageInternal from "@App/app/message/internal";
import { MessageHander, MessageManager } from "@App/app/message/message";
import { ScriptRunResouce } from "@App/app/repo/scripts";
Expand Down Expand Up @@ -31,6 +32,44 @@ export default class ContentRuntime {
this.contentMessage.setHandler(ExternalMessage, (action, data) => {
return this.internalMessage.syncSend(action, data);
});
// 处理GM_addElement
// @ts-ignore
this.contentMessage.setHandler("GM_addElement", (action, data) => {
const parma = data.param;
let attr: { [x: string]: any; textContent?: any };
let textContent = "";
if (!parma[1]) {
attr = {};
} else {
attr = { ...parma[1] };
if (attr.textContent) {
textContent = attr.textContent;
delete attr.textContent;
}
}
const el = <Element>document.createElement(parma[0]);
Object.keys(attr).forEach((key) => {
el.setAttribute(key, attr[key]);
});
if (textContent) {
el.innerHTML = textContent;
}
let parentNode;
if (data.relatedTarget) {
parentNode = (<MessageContent>(
this.contentMessage
)).getAndDelRelatedTarget(data.relatedTarget);
}
(
<Element>parentNode ||
document.head ||
document.body ||
document.querySelector("*")
).appendChild(el);
return {
relatedTarget: el,
};
});

// 转发长连接的gmApi消息
this.contentMessage.setHandlerWithChannel(
Expand Down
49 changes: 46 additions & 3 deletions src/runtime/content/gm_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
/* eslint-disable max-classes-per-file */
import LoggerCore from "@App/app/logger/core";
import { Channel, ChannelHandler } from "@App/app/message/channel";
import MessageContent from "@App/app/message/content";
import { MessageManager } from "@App/app/message/message";
import { ScriptRunResouce } from "@App/app/repo/scripts";
import { blobToBase64, getMetadataStr } from "@App/pkg/utils/script";
import { v4 as uuidv4 } from "uuid";
import { ValueUpdateData } from "./exec_script";
import { addStyle } from "./utils";

interface ApiParam {
depend?: string[];
Expand Down Expand Up @@ -530,8 +530,27 @@ export default class GMApi {
}

@GMContext.API()
GM_addStyle(css: string): HTMLElement {
return addStyle(css);
GM_addStyle(css: string) {
let el: Element | undefined;
// 与content页的消息通讯实际是同步,此方法不需要经过background
// 所以可以直接在then中赋值el再返回
(<MessageContent>this.message).sendCallback(
"GM_addElement",
{
param: [
"style",
{
textContent: css,
},
],
},
(resp) => {
el = (<MessageContent>this.message).getAndDelRelatedTarget(
resp.relatedTarget
);
}
);
return el;
}

@GMContext.API()
Expand Down Expand Up @@ -670,4 +689,28 @@ export default class GMApi {
CAT_userConfig() {
return this.sendMessage("CAT_userConfig", []);
}

// 此API在content页实现
@GMContext.API()
GM_addElement(parentNode: Element | string, tagName: any, attrs?: any) {
let el: Element | undefined;
// 与content页的消息通讯实际是同步,此方法不需要经过background
// 所以可以直接在then中赋值el再返回
(<MessageContent>this.message).sendCallback(
"GM_addElement",
{
param: [
typeof parentNode === "string" ? parentNode : tagName,
typeof parentNode === "string" ? tagName : attrs,
],
relatedTarget: typeof parentNode === "string" ? null : parentNode,
},
(resp) => {
el = (<MessageContent>this.message).getAndDelRelatedTarget(
resp.relatedTarget
);
}
);
return el;
}
}
3 changes: 3 additions & 0 deletions src/types/scriptcat.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ declare function GM_setClipboard(
info?: string | { type?: string; minetype?: string }
): void;

declare function GM_addElement(tag: string, attribubutes: any);
declare function GM_addElement(parentNode: Element, tag: string, attrs: any);

// name和domain不能都为空
declare function GM_cookie(
action: GMTypes.CookieAction,
Expand Down

0 comments on commit 0d0f4db

Please sign in to comment.