添加GM element API

This commit is contained in:
王一之 2025-04-11 17:40:25 +08:00
parent a2870eb18e
commit 7ca85801ef
15 changed files with 393 additions and 128 deletions

View File

@ -17,7 +17,7 @@ export class CustomEventMessage implements Message {
EE: EventEmitter = new EventEmitter(); EE: EventEmitter = new EventEmitter();
// 关联dom目标 // 关联dom目标
relatedTarget: Map<number, Document> = new Map(); relatedTarget: Map<number, EventTarget> = new Map();
constructor( constructor(
protected flag: string, protected flag: string,
@ -25,7 +25,7 @@ export class CustomEventMessage implements Message {
) { ) {
window.addEventListener((isContent ? "ct" : "fd") + flag, (event) => { window.addEventListener((isContent ? "ct" : "fd") + flag, (event) => {
if (event instanceof MouseEvent) { if (event instanceof MouseEvent) {
this.relatedTarget.set(event.clientX, <Document>event.relatedTarget); this.relatedTarget.set(event.clientX, event.relatedTarget!);
return; return;
} else if (event instanceof CustomEvent) { } else if (event instanceof CustomEvent) {
this.messageHandle(event.detail, new CustomEventPostMessage(this)); this.messageHandle(event.detail, new CustomEventPostMessage(this));
@ -82,23 +82,7 @@ export class CustomEventMessage implements Message {
}); });
} }
nativeSend(data: any) { nativeSend(detail: any) {
let detail = data;
// 特殊处理relatedTarget
if (detail.data && 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.flag, {
clientX: detail.data.relatedTarget,
relatedTarget: target,
});
window.dispatchEvent(ev);
}
if (typeof cloneInto !== "undefined") { if (typeof cloneInto !== "undefined") {
try { try {
LoggerCore.logger().info("nativeSend"); LoggerCore.logger().info("nativeSend");
@ -131,6 +115,40 @@ export class CustomEventMessage implements Message {
}); });
} }
// 同步发送消息
// 与content页的消息通讯实际是同步,此方法不需要经过background
// 但是请注意中间不要有promise
syncSendMessage(data: any): any {
const body: WindowMessageBody = {
messageId: uuidv4(),
type: "sendMessage",
data,
};
let ret: any;
const callback = (body: WindowMessageBody) => {
this.EE.removeListener("response:" + body.messageId, callback);
ret = body.data;
};
this.EE.addListener("response:" + body.messageId, callback);
this.nativeSend(body);
return ret;
}
relateId = 0;
sendRelatedTarget(target: EventTarget): number {
// 特殊处理relatedTarget返回id进行关联
// 先将relatedTarget转换成id发送过去
const id = ++this.relateId;
// 可以使用此种方式交互element
const ev = new MouseEvent((this.isContent ? "fd" : "ct") + this.flag, {
clientX: id,
relatedTarget: target,
});
window.dispatchEvent(ev);
return id;
}
getAndDelRelatedTarget(id: number) { getAndDelRelatedTarget(id: number) {
const target = this.relatedTarget.get(id); const target = this.relatedTarget.get(id);
this.relatedTarget.delete(id); this.relatedTarget.delete(id);

View File

@ -1,5 +1,5 @@
import LoggerCore from "@App/app/logger/core"; import LoggerCore from "@App/app/logger/core";
import { connect } from "./client"; import { connect, sendMessage } from "./client";
export interface Message extends MessageSend { export interface Message extends MessageSend {
onConnect(callback: (data: any, con: MessageConnect) => void): void; onConnect(callback: (data: any, con: MessageConnect) => void): void;
@ -20,6 +20,12 @@ export interface MessageConnect {
export type MessageSender = chrome.runtime.MessageSender; export type MessageSender = chrome.runtime.MessageSender;
export type ExtMessageSender = {
tabId: number;
frameId?: number;
documentId?: string;
};
export class GetSender { export class GetSender {
constructor(private sender: MessageConnect | MessageSender) {} constructor(private sender: MessageConnect | MessageSender) {}
@ -27,12 +33,22 @@ export class GetSender {
return this.sender as MessageSender; return this.sender as MessageSender;
} }
getExtMessageSender(): ExtMessageSender {
const sender = this.sender as MessageSender;
return {
tabId: sender.tab?.id || -1, // -1表示后台脚本
frameId: sender.frameId,
documentId: sender.documentId,
};
}
getConnect(): MessageConnect { getConnect(): MessageConnect {
return this.sender as MessageConnect; return this.sender as MessageConnect;
} }
} }
export type ApiFunction = (params: any, con: GetSender) => Promise<any> | void; export type ApiFunction = (params: any, con: GetSender) => Promise<any> | void;
export type ApiFunctionSync = (params: any, con: GetSender) => any;
export class Server { export class Server {
private apiFunctionMap: Map<string, ApiFunction> = new Map(); private apiFunctionMap: Map<string, ApiFunction> = new Map();
@ -115,11 +131,24 @@ export class Group {
} }
// 转发消息 // 转发消息
export function forwardMessage(prefix: string, path: string, from: Server, to: MessageSend, middleware?: ApiFunction) { export function forwardMessage(
from.on(path, async (params, fromCon) => { prefix: string,
path: string,
from: Server,
to: MessageSend,
middleware?: ApiFunctionSync
) {
from.on(path, (params, fromCon) => {
if (middleware) { if (middleware) {
const resp = await middleware(params, fromCon); // 此处是为了处理CustomEventMessage的同步消息情况
if (resp !== false) { const resp = middleware(params, fromCon) as any;
if (resp instanceof Promise) {
return resp.then((data) => {
if (data !== false) {
return data;
}
});
} else if (resp !== false) {
return resp; return resp;
} }
} }
@ -140,7 +169,7 @@ export function forwardMessage(prefix: string, path: string, from: Server, to: M
}); });
}); });
} else { } else {
return to.sendMessage({ action: prefix + "/" + path, data: params }); return sendMessage(to, prefix + "/" + path, params);
} }
}); });
} }

View File

@ -1,5 +1,6 @@
import { ScriptRunResouce } from "@App/app/repo/scripts"; import { ScriptRunResouce } from "@App/app/repo/scripts";
import { Client, sendMessage } from "@Packages/message/client"; import { Client, sendMessage } from "@Packages/message/client";
import { CustomEventMessage } from "@Packages/message/custom_event_message";
import { forwardMessage, Message, MessageSend, Server } from "@Packages/message/server"; import { forwardMessage, Message, MessageSend, Server } from "@Packages/message/server";
// content页的处理 // content页的处理
@ -52,6 +53,32 @@ export default class ContentRuntime {
xhr.send(); xhr.send();
}); });
} }
case "GM_addElement": {
let [parentNodeId, tagName, attr] = data.params;
let parentNode: EventTarget | undefined;
if (parentNodeId) {
parentNode = (this.msg as CustomEventMessage).getAndDelRelatedTarget(parentNodeId);
}
const el = <Element>document.createElement(tagName);
Object.keys(attr).forEach((key) => {
el.setAttribute(key, attr[key]);
});
let textContent = "";
if (attr) {
if (attr.textContent) {
textContent = attr.textContent;
delete attr.textContent;
}
} else {
attr = {};
}
if (textContent) {
el.innerHTML = textContent;
}
(<Element>parentNode || document.head || document.body || document.querySelector("*")).appendChild(el);
const nodeId = (this.msg as CustomEventMessage).sendRelatedTarget(el);
return nodeId;
}
} }
return Promise.resolve(false); return Promise.resolve(false);
} }

View File

@ -4,6 +4,7 @@ import { ScriptRunResouce } from "@App/app/repo/scripts";
import GMApi from "./gm_api"; import GMApi from "./gm_api";
import { compileScript, createContext, proxyContext, ScriptFunc } from "./utils"; import { compileScript, createContext, proxyContext, ScriptFunc } from "./utils";
import { Message } from "@Packages/message/server"; import { Message } from "@Packages/message/server";
import { EmitEventRequest } from "../service_worker/runtime";
export type ValueUpdateSender = { export type ValueUpdateSender = {
runFlag: string; runFlag: string;
@ -69,12 +70,9 @@ export default class ExecScript {
} }
} }
emitEvent(event: string, data: any) { emitEvent(event: string, eventId: string, data: any) {
switch (event) { this.logger.debug("emit event", { event, eventId, data });
case "menuClick": this.sandboxContent?.emitEvent(event, eventId, data);
this.sandboxContent?.menuClick(data);
break;
}
} }
valueUpdate(data: ValueUpdateData) { valueUpdate(data: ValueUpdateData) {

View File

@ -1,5 +1,5 @@
import { ScriptRunResouce } from "@App/app/repo/scripts"; import { ScriptRunResouce } from "@App/app/repo/scripts";
import { getMetadataStr, getUserConfigStr, parseUserConfig } from "@App/pkg/utils/script"; import { base64ToBlob, getMetadataStr, getUserConfigStr, parseUserConfig } from "@App/pkg/utils/script";
import { ValueUpdateData } from "./exec_script"; import { ValueUpdateData } from "./exec_script";
import { ExtVersion } from "@App/app/const"; import { ExtVersion } from "@App/app/const";
import { Message, MessageConnect } from "@Packages/message/server"; import { Message, MessageConnect } from "@Packages/message/server";
@ -11,7 +11,6 @@ import { getStorageName } from "@App/pkg/utils/utils";
interface ApiParam { interface ApiParam {
depend?: string[]; depend?: string[];
listener?: () => void;
} }
export interface ApiValue { export interface ApiValue {
@ -25,9 +24,6 @@ export class GMContext {
public static API(param: ApiParam = {}) { public static API(param: ApiParam = {}) {
return (target: any, propertyName: string, descriptor: PropertyDescriptor) => { return (target: any, propertyName: string, descriptor: PropertyDescriptor) => {
const key = propertyName; const key = propertyName;
if (param.listener) {
param.listener();
}
if (key === "GMdotXmlHttpRequest") { if (key === "GMdotXmlHttpRequest") {
GMContext.apis.set("GM.xmlHttpRequest", { GMContext.apis.set("GM.xmlHttpRequest", {
api: descriptor.value, api: descriptor.value,
@ -103,8 +99,8 @@ export default class GMApi {
} }
} }
menuClick(id: number) { emitEvent(event: string, eventId: string, data: any) {
this.EE.emit("menuClick" + id); this.EE.emit(event + ":" + eventId, data);
} }
// 获取脚本信息和管理器信息 // 获取脚本信息和管理器信息
@ -239,19 +235,71 @@ export default class GMApi {
this.eventId += 1; this.eventId += 1;
const id = this.eventId; const id = this.eventId;
this.menuMap.set(id, name); this.menuMap.set(id, name);
this.EE.addListener("menuClick" + id, listener); this.EE.addListener("menuClick:" + id, listener);
this.sendMessage("GM_registerMenuCommand", [id, name, accessKey]); this.sendMessage("GM_registerMenuCommand", [id, name, accessKey]);
return id; return id;
} }
@GMContext.API()
GM_addStyle(css: string) {
// 与content页的消息通讯实际是同步,此方法不需要经过background
// 这里直接使用同步的方式去处理, 不要有promise
const resp = (<CustomEventMessage>this.message).syncSendMessage({
action: this.prefix + "/runtime/gmApi",
data: {
uuid: this.scriptRes.uuid,
api: "GM_addElement",
params: [
null,
"style",
{
textContent: css,
},
],
},
});
if (resp.code !== 0) {
throw new Error(resp.message);
}
return (<CustomEventMessage>this.message).getAndDelRelatedTarget(resp.data);
}
@GMContext.API()
GM_addElement(parentNode: EventTarget | string, tagName: any, attrs?: any) {
// 与content页的消息通讯实际是同步,此方法不需要经过background
// 这里直接使用同步的方式去处理, 不要有promise
let parentNodeId: any = parentNode;
if (typeof parentNodeId !== "string") {
const id = (<CustomEventMessage>this.message).sendRelatedTarget(parentNodeId);
parentNodeId = id;
} else {
parentNodeId = null;
}
const resp = (<CustomEventMessage>this.message).syncSendMessage({
action: this.prefix + "/runtime/gmApi",
data: {
uuid: this.scriptRes.uuid,
api: "GM_addElement",
params: [
parentNodeId,
typeof parentNode === "string" ? parentNode : tagName,
typeof parentNode === "string" ? tagName : attrs,
],
},
});
if (resp.code !== 0) {
throw new Error(resp.message);
}
return (<CustomEventMessage>this.message).getAndDelRelatedTarget(resp.data);
}
@GMContext.API() @GMContext.API()
GM_unregisterMenuCommand(id: number): void { GM_unregisterMenuCommand(id: number): void {
console.log("unregisterMenuCommand", id);
if (!this.menuMap) { if (!this.menuMap) {
this.menuMap = new Map(); this.menuMap = new Map();
} }
this.menuMap.delete(id); this.menuMap.delete(id);
this.EE.removeAllListeners("menuClick" + id); this.EE.removeAllListeners("menuClick:" + id);
this.sendMessage("GM_unregisterMenuCommand", [id]); this.sendMessage("GM_unregisterMenuCommand", [id]);
} }
@ -449,15 +497,19 @@ export default class GMApi {
}; };
} }
@GMContext.API() @GMContext.API({
depend: ["GM_closeNotification", "GM_updateNotification"],
})
public async GM_notification( public async GM_notification(
detail: GMTypes.NotificationDetails | string, detail: GMTypes.NotificationDetails | string,
ondone?: GMTypes.NotificationOnDone | string, ondone?: GMTypes.NotificationOnDone | string,
image?: string, image?: string,
onclick?: GMTypes.NotificationOnClick onclick?: GMTypes.NotificationOnClick
) { ) {
let data: GMTypes.NotificationDetails = {}; this.eventId += 1;
let data: GMTypes.NotificationDetails;
if (typeof detail === "string") { if (typeof detail === "string") {
data = {};
data.text = detail; data.text = detail;
switch (arguments.length) { switch (arguments.length) {
case 4: case 4:
@ -470,7 +522,7 @@ export default class GMApi {
break; break;
} }
} else { } else {
data = detail; data = Object.assign({}, detail);
data.ondone = data.ondone || <GMTypes.NotificationOnDone>ondone; data.ondone = data.ondone || <GMTypes.NotificationOnDone>ondone;
} }
let click: GMTypes.NotificationOnClick; let click: GMTypes.NotificationOnClick;
@ -488,28 +540,54 @@ export default class GMApi {
create = data.oncreate; create = data.oncreate;
delete data.oncreate; delete data.oncreate;
} }
this.eventId += 1; this.sendMessage("GM_notification", [data]).then((id) => {
this.sendMessage("GM_notification", [data]); if (create) {
this.EE.addListener("GM_notification:" + this.eventId, (resp: any) => { create.apply({ id }, [id]);
switch (resp.event) {
case "click": {
click && click.apply({ id: resp.id }, [resp.id, resp.index]);
break;
}
case "done": {
done && done.apply({ id: resp.id }, [resp.user]);
break;
}
case "create": {
create && create.apply({ id: resp.id }, [resp.id]);
break;
}
default:
LoggerCore.logger().warn("GM_notification resp is error", {
resp,
});
break;
} }
this.EE.addListener("GM_notification:" + id, (resp: any) => {
switch (resp.event) {
case "click":
case "buttonClick": {
click && click.apply({ id }, [id, resp.params.index]);
break;
}
case "close": {
done && done.apply({ id }, [resp.params.byUser]);
this.EE.removeAllListeners("GM_notification:" + this.eventId);
break;
}
default:
LoggerCore.logger().warn("GM_notification resp is error", {
resp,
});
break;
}
});
}); });
} }
@GMContext.API()
public GM_closeNotification(id: string): void {
this.sendMessage("GM_closeNotification", [id]);
}
@GMContext.API()
public GM_updateNotification(id: string, details: GMTypes.NotificationDetails): void {
this.sendMessage("GM_updateNotification", [id, details]);
}
@GMContext.API()
GM_getResourceURL(name: string, isBlobUrl?: boolean): string | undefined {
if (!this.scriptRes.resource) {
return undefined;
}
const r = this.scriptRes.resource[name];
if (r) {
if (isBlobUrl) {
return URL.createObjectURL(base64ToBlob(r.base64));
}
return r.base64;
}
return undefined;
}
} }

View File

@ -34,7 +34,7 @@ export class InjectRuntime {
// 转发给脚本 // 转发给脚本
const exec = this.execList.find((val) => val.scriptRes.uuid === data.uuid); const exec = this.execList.find((val) => val.scriptRes.uuid === data.uuid);
if (exec) { if (exec) {
exec.emitEvent(data.event, data.data); exec.emitEvent(data.event, data.eventId, data.data);
} }
}); });
this.server.on("runtime/valueUpdate", (data: ValueUpdateData) => { this.server.on("runtime/valueUpdate", (data: ValueUpdateData) => {

View File

@ -66,7 +66,7 @@ export function createContext(scriptRes: ScriptRunResouce, GMInfo: any, envPrefi
runFlag: uuidv4(), runFlag: uuidv4(),
eventId: 10000, eventId: 10000,
valueUpdate: GMApi.prototype.valueUpdate, valueUpdate: GMApi.prototype.valueUpdate,
menuClick: GMApi.prototype.menuClick, emitEvent: GMApi.prototype.emitEvent,
EE: new EventEmitter(), EE: new EventEmitter(),
GM: { Info: GMInfo }, GM: { Info: GMInfo },
GM_info: GMInfo, GM_info: GMInfo,

View File

@ -305,7 +305,7 @@ export class Runtime {
// 转发给脚本 // 转发给脚本
const exec = this.execScripts.get(data.uuid); const exec = this.execScripts.get(data.uuid);
if (exec) { if (exec) {
exec.emitEvent(data.event, data.data); exec.emitEvent(data.event, data.eventId, data.data);
} }
} }

View File

@ -119,9 +119,11 @@ export class PopupClient extends Client {
return this.do("menuClick", { return this.do("menuClick", {
uuid, uuid,
id: data.id, id: data.id,
tabId: data.tabId, sender: {
frameId: data.frameId, tabId: data.tabId,
documentId: data.documentId, frameId: data.frameId,
documentId: data.documentId,
},
}); });
} }
} }

View File

@ -1,7 +1,7 @@
import LoggerCore from "@App/app/logger/core"; import LoggerCore from "@App/app/logger/core";
import Logger from "@App/app/logger/logger"; import Logger from "@App/app/logger/logger";
import { Script, ScriptDAO } from "@App/app/repo/scripts"; import { Script, ScriptDAO } from "@App/app/repo/scripts";
import { GetSender, Group, MessageSend } from "@Packages/message/server"; import { ExtMessageSender, GetSender, Group, MessageSend } from "@Packages/message/server";
import { ValueService } from "@App/app/service/service_worker/value"; import { ValueService } from "@App/app/service/service_worker/value";
import PermissionVerify from "./permission_verify"; import PermissionVerify from "./permission_verify";
import { connect } from "@Packages/message/client"; import { connect } from "@Packages/message/client";
@ -9,6 +9,7 @@ import Cache, { incr } from "@App/app/cache";
import EventEmitter from "eventemitter3"; import EventEmitter from "eventemitter3";
import { MessageQueue } from "@Packages/message/message_queue"; import { MessageQueue } from "@Packages/message/message_queue";
import { RuntimeService } from "./runtime"; import { RuntimeService } from "./runtime";
import { getIcon, isFirefox } from "@App/pkg/utils/utils";
// GMApi,处理脚本的GM API调用请求 // GMApi,处理脚本的GM API调用请求
@ -52,6 +53,12 @@ export const unsafeHeaders: { [key: string]: boolean } = {
via: true, via: true,
}; };
type NotificationData = {
uuid: string;
details: GMTypes.NotificationDetails;
sender: ExtMessageSender;
};
export type Api = (request: Request, con: GetSender) => Promise<any>; export type Api = (request: Request, con: GetSender) => Promise<any>;
export default class GMApi { export default class GMApi {
@ -71,7 +78,7 @@ export default class GMApi {
this.logger = LoggerCore.logger().with({ service: "runtime/gm_api" }); this.logger = LoggerCore.logger().with({ service: "runtime/gm_api" });
} }
async handlerRequest(data: MessageRequest, con: GetSender) { async handlerRequest(data: MessageRequest, sender: GetSender) {
this.logger.trace("GM API request", { api: data.api, uuid: data.uuid, param: data.params }); this.logger.trace("GM API request", { api: data.api, uuid: data.uuid, param: data.params });
const api = PermissionVerify.apis.get(data.api); const api = PermissionVerify.apis.get(data.api);
if (!api) { if (!api) {
@ -84,7 +91,7 @@ export default class GMApi {
this.logger.error("verify error", { api: data.api }, Logger.E(e)); this.logger.error("verify error", { api: data.api }, Logger.E(e));
return Promise.reject(e); return Promise.reject(e);
} }
return api.api.call(this, req, con); return api.api.call(this, req, sender);
} }
// 解析请求 // 解析请求
@ -240,6 +247,105 @@ export default class GMApi {
}); });
} }
@PermissionVerify.API({})
GM_notification(request: Request, sender: GetSender) {
if (request.params.length === 0) {
return Promise.reject(new Error("param is failed"));
}
const details: GMTypes.NotificationDetails = request.params[0];
const options: chrome.notifications.NotificationOptions<true> = {
title: details.title || "ScriptCat",
message: details.text || "无消息内容",
iconUrl: details.image || getIcon(request.script) || chrome.runtime.getURL("assets/logo.png"),
type: isFirefox() || details.progress === undefined ? "basic" : "progress",
};
if (!isFirefox()) {
options.silent = details.silent;
options.buttons = details.buttons;
}
options.progress = options.progress && parseInt(details.progress as any, 10);
return new Promise((resolve) => {
chrome.notifications.create(options, (notificationId) => {
Cache.getInstance().set(`GM_notification:${notificationId}`, {
uuid: request.script.uuid,
details: details,
sender: sender.getExtMessageSender(),
});
if (details.timeout) {
setTimeout(() => {
chrome.notifications.clear(notificationId);
Cache.getInstance().del(`GM_notification:${notificationId}`);
}, details.timeout);
}
resolve(notificationId);
});
});
}
@PermissionVerify.API({
link: "GM_notification",
})
GM_closeNotification(request: Request) {
if (request.params.length === 0) {
return Promise.reject(new Error("param is failed"));
}
const [notificationId] = request.params;
Cache.getInstance().del(`GM_notification:${notificationId}`);
chrome.notifications.clear(notificationId);
}
@PermissionVerify.API({
link: "GM_notification",
})
GM_updateNotification(request: Request) {
if (isFirefox()) {
return Promise.reject(new Error("firefox does not support this method"));
}
const id = request.params[0];
const details: GMTypes.NotificationDetails = request.params[1];
const options: chrome.notifications.NotificationOptions = {
title: details.title,
message: details.text,
iconUrl: details.image,
type: details.progress === undefined ? "basic" : "progress",
silent: details.silent,
progress: details.progress && parseInt(details.progress as any, 10),
};
chrome.notifications.update(<string>id, options);
}
handlerNotification() {
const send = async (event: string, notificationId: string, params?: any) => {
const ret = (await Cache.getInstance().get(`GM_notification:${notificationId}`)) as NotificationData;
if (ret) {
this.runtime.emitEventToTab(ret.sender, {
event: "GM_notification",
eventId: notificationId,
uuid: ret.uuid,
data: {
event,
params,
},
});
}
};
chrome.notifications.onClosed.addListener((notificationId, byUser) => {
send("close", notificationId, {
byUser,
});
Cache.getInstance().del(`GM_notification:${notificationId}`);
});
chrome.notifications.onClicked.addListener((notificationId) => {
send("click", notificationId);
});
chrome.notifications.onButtonClicked.addListener((notificationId, index) => {
send("buttonClick", notificationId, {
index: index,
});
});
}
// 处理GM_xmlhttpRequest请求 // 处理GM_xmlhttpRequest请求
handlerGmXhr() { handlerGmXhr() {
chrome.webRequest.onBeforeSendHeaders.addListener( chrome.webRequest.onBeforeSendHeaders.addListener(
@ -290,5 +396,6 @@ export default class GMApi {
start() { start() {
this.group.on("gmApi", this.handlerRequest.bind(this)); this.group.on("gmApi", this.handlerRequest.bind(this));
this.handlerGmXhr(); this.handlerGmXhr();
this.handlerNotification();
} }
} }

View File

@ -1,5 +1,5 @@
import { MessageQueue } from "@Packages/message/message_queue"; import { MessageQueue } from "@Packages/message/message_queue";
import { Group } from "@Packages/message/server"; import { ExtMessageSender, Group } from "@Packages/message/server";
import { RuntimeService, ScriptMatchInfo } from "./runtime"; import { RuntimeService, ScriptMatchInfo } from "./runtime";
import Cache from "@App/app/cache"; import Cache from "@App/app/cache";
import { GetPopupDataReq, GetPopupDataRes } from "./client"; import { GetPopupDataReq, GetPopupDataRes } from "./client";
@ -315,32 +315,13 @@ export class PopupService {
}); });
} }
menuClick({ menuClick({ uuid, id, sender }: { uuid: string; id: number; sender: ExtMessageSender }) {
uuid,
id,
tabId,
frameId,
documentId,
}: {
uuid: string;
id: number;
tabId: number;
frameId: number;
documentId: string;
}) {
// 菜单点击事件 // 菜单点击事件
this.runtime.EmitEventToTab( this.runtime.emitEventToTab(sender, {
tabId, uuid,
{ event: "menuClick",
uuid, eventId: id.toString(),
event: "menuClick", });
data: id,
},
{
frameId,
documentId: documentId,
}
);
return Promise.resolve(true); return Promise.resolve(true);
} }
@ -384,9 +365,11 @@ export class PopupService {
this.menuClick({ this.menuClick({
uuid: script.uuid, uuid: script.uuid,
id: menuItem.id, id: menuItem.id,
tabId: bgscript ? -1 : tab!.id!, sender: {
frameId: menuItem.frameId || 0, tabId: bgscript ? -1 : tab!.id!,
documentId: menuItem.documentId || "", frameId: menuItem.frameId || 0,
documentId: menuItem.documentId || "",
},
}); });
return; return;
} }

View File

@ -1,5 +1,5 @@
import { MessageQueue } from "@Packages/message/message_queue"; import { MessageQueue } from "@Packages/message/message_queue";
import { GetSender, Group, MessageSend } from "@Packages/message/server"; import { ExtMessageSender, GetSender, Group, MessageSend } from "@Packages/message/server";
import { import {
Script, Script,
SCRIPT_STATUS, SCRIPT_STATUS,
@ -32,7 +32,8 @@ export interface ScriptMatchInfo extends ScriptRunResouce {
export interface EmitEventRequest { export interface EmitEventRequest {
uuid: string; uuid: string;
event: string; event: string;
data: any; eventId: string;
data?: any;
} }
export class RuntimeService { export class RuntimeService {
@ -122,36 +123,35 @@ export class RuntimeService {
} }
// 给指定tab发送消息 // 给指定tab发送消息
sendMessageToTab( sendMessageToTab(to: ExtMessageSender, action: string, data: any) {
tabId: number, if (to.tabId === -1) {
action: string,
data: any,
options?: {
documentId?: string;
frameId?: number;
}
) {
if (tabId === -1) {
// 如果是-1, 代表给offscreen发送消息 // 如果是-1, 代表给offscreen发送消息
return sendMessage(this.sender, "offscreen/runtime/" + action, data); return sendMessage(this.sender, "offscreen/runtime/" + action, data);
} }
return sendMessage(new ExtensionContentMessageSend(tabId, options), "content/runtime/" + action, data); return sendMessage(
new ExtensionContentMessageSend(to.tabId, {
documentId: to.documentId,
frameId: to.frameId,
}),
"content/runtime/" + action,
data
);
} }
// 给指定脚本触发事件 // 给指定脚本触发事件
EmitEventToTab( emitEventToTab(to: ExtMessageSender, req: EmitEventRequest) {
tabId: number, if (to.tabId === -1) {
req: EmitEventRequest,
options?: {
documentId?: string;
frameId?: number;
}
) {
if (tabId === -1) {
// 如果是-1, 代表给offscreen发送消息 // 如果是-1, 代表给offscreen发送消息
return sendMessage(this.sender, "offscreen/runtime/emitEvent", req); return sendMessage(this.sender, "offscreen/runtime/emitEvent", req);
} }
return sendMessage(new ExtensionContentMessageSend(tabId, options), "content/runtime/emitEvent", req); return sendMessage(
new ExtensionContentMessageSend(to.tabId, {
documentId: to.documentId,
frameId: to.frameId,
}),
"content/runtime/emitEvent",
req
);
} }
async getPageScriptUuidByUrl(url: string, includeCustomize?: boolean) { async getPageScriptUuidByUrl(url: string, includeCustomize?: boolean) {

View File

@ -73,12 +73,24 @@ export class ValueService {
tabs.forEach(async (tab) => { tabs.forEach(async (tab) => {
const scriptMenu = await this.popup!.getScriptMenu(tab.id!); const scriptMenu = await this.popup!.getScriptMenu(tab.id!);
if (scriptMenu.find((item) => item.storageName === storageName)) { if (scriptMenu.find((item) => item.storageName === storageName)) {
this.runtime!.sendMessageToTab(tab.id!, "valueUpdate", sendData); this.runtime!.sendMessageToTab(
{
tabId: tab.id!,
},
"valueUpdate",
sendData
);
} }
}); });
}); });
// 推送到offscreen中 // 推送到offscreen中
sendMessage(this.send, "offscreen/runtime/valueUpdate", sendData); this.runtime!.sendMessageToTab(
{
tabId: -1,
},
"valueUpdate",
sendData
);
return Promise.resolve(true); return Promise.resolve(true);
} }

View File

@ -30,6 +30,7 @@
"webRequest", "webRequest",
"userScripts", "userScripts",
"contextMenus", "contextMenus",
"notifications",
"unlimitedStorage", "unlimitedStorage",
"declarativeNetRequest" "declarativeNetRequest"
], ],

View File

@ -224,3 +224,13 @@ export function getStorageName(script: Script): string {
} }
return script.uuid; return script.uuid;
} }
export function getIcon(script: Script): string | undefined {
return (
(script.metadata.icon && script.metadata.icon[0]) ||
(script.metadata.iconurl && script.metadata.iconurl[0]) ||
(script.metadata.defaulticon && script.metadata.defaulticon[0]) ||
(script.metadata.icon64 && script.metadata.icon64[0]) ||
(script.metadata.icon64url && script.metadata.icon64url[0])
);
}