chrome菜单
Some checks failed
test / Run tests (push) Failing after 3s
build / Build (push) Failing after 5s

This commit is contained in:
2025-04-07 22:20:22 +08:00
parent c43afb0a94
commit f26aecd10f
7 changed files with 164 additions and 19 deletions

View File

@@ -86,13 +86,19 @@ export class ExtensionMessageConnect implements MessageConnect {
} }
export class ExtensionContentMessageSend extends ExtensionMessageSend { export class ExtensionContentMessageSend extends ExtensionMessageSend {
constructor(private tabId: number) { constructor(
private tabId: number,
private options?: {
frameId?: number;
documentId?: string;
}
) {
super(); super();
} }
sendMessage(data: any): Promise<any> { sendMessage(data: any): Promise<any> {
return new Promise((resolve) => { return new Promise((resolve) => {
chrome.tabs.sendMessage(this.tabId, data, (resp) => { chrome.tabs.sendMessage(this.tabId, data, this.options || {}, (resp) => {
resolve(resp); resolve(resp);
}); });
}); });
@@ -100,7 +106,7 @@ export class ExtensionContentMessageSend extends ExtensionMessageSend {
connect(data: any): Promise<MessageConnect> { connect(data: any): Promise<MessageConnect> {
return new Promise((resolve) => { return new Promise((resolve) => {
const con = chrome.tabs.connect(this.tabId); const con = chrome.tabs.connect(this.tabId, this.options);
con.postMessage(data); con.postMessage(data);
resolve(new ExtensionMessageConnect(con)); resolve(new ExtensionMessageConnect(con));
}); });

View File

@@ -35,6 +35,7 @@ export type ScriptMenuRegisterCallbackValue = {
accessKey: string; accessKey: string;
tabId: number; tabId: number;
frameId: number; frameId: number;
documentId: string;
}; };
export function subscribeScriptMenuRegister( export function subscribeScriptMenuRegister(

View File

@@ -112,6 +112,12 @@ export class PopupClient extends Client {
} }
menuClick(uuid: string, data: ScriptMenuItem) { menuClick(uuid: string, data: ScriptMenuItem) {
return this.do("menuClick", { uuid, id: data.id, tabId: data.tabId, frameId: data.frameId }); return this.do("menuClick", {
uuid,
id: data.id,
tabId: data.tabId,
frameId: data.frameId,
documentId: data.documentId,
});
} }
} }

View File

@@ -190,6 +190,7 @@ export default class GMApi {
accessKey: accessKey, accessKey: accessKey,
tabId: sender.getSender().tab!.id!, tabId: sender.getSender().tab!.id!,
frameId: sender.getSender().frameId, frameId: sender.getSender().frameId,
documentId: sender.getSender().documentId,
}); });
} }

View File

@@ -24,8 +24,9 @@ export type ScriptMenuItem = {
id: number; id: number;
name: string; name: string;
accessKey?: string; accessKey?: string;
tabId: number | "background"; tabId: number; //-1表示后台脚本
frameId: number; frameId?: number;
documentId?: string;
}; };
export type ScriptMenu = { export type ScriptMenu = {
@@ -52,6 +53,54 @@ export class PopupService {
private runtime: RuntimeService private runtime: RuntimeService
) {} ) {}
genScriptMenuByTabMap(menu: ScriptMenu[]) {
menu.forEach((script) => {
// 创建脚本菜单
if (script.menus.length) {
chrome.contextMenus.create({
id: `scriptMenu_` + script.uuid,
title: script.name,
contexts: ["all"],
parentId: "scriptMenu",
});
script.menus.forEach((menu) => {
// 创建菜单
console.log("create menu", menu);
chrome.contextMenus.create({
id: `scriptMenu_menu_${script.uuid}_${menu.id}`,
title: menu.name,
contexts: ["all"],
parentId: `scriptMenu_${script.uuid}`,
});
});
}
});
}
// 生成chrome菜单
async genScriptMenu(tabId: number) {
// 移除之前所有的菜单
chrome.contextMenus.removeAll();
const [menu, backgroundMenu] = await Promise.all([this.getScriptMenu(tabId), this.getScriptMenu(-1)]);
console.log(menu, backgroundMenu, tabId);
if (!menu.length && !backgroundMenu.length) {
return;
}
// 创建根菜单
chrome.contextMenus.create({
id: "scriptMenu",
title: "ScriptCat",
contexts: ["all"],
});
if (menu) {
this.genScriptMenuByTabMap(menu);
}
// 后台脚本的菜单
if (backgroundMenu) {
this.genScriptMenuByTabMap(backgroundMenu);
}
}
async registerMenuCommand(message: ScriptMenuRegisterCallbackValue) { async registerMenuCommand(message: ScriptMenuRegisterCallbackValue) {
// 给脚本添加菜单 // 给脚本添加菜单
const data = await this.getScriptMenu(message.tabId); const data = await this.getScriptMenu(message.tabId);
@@ -65,6 +114,7 @@ export class PopupService {
accessKey: message.accessKey, accessKey: message.accessKey,
tabId: message.tabId, tabId: message.tabId,
frameId: message.frameId, frameId: message.frameId,
documentId: message.documentId,
}); });
} else { } else {
menu.name = message.name; menu.name = message.name;
@@ -72,8 +122,10 @@ export class PopupService {
menu.tabId = message.tabId; menu.tabId = message.tabId;
} }
} }
console.log(data); console.log("set menu", data);
Cache.getInstance().set("tabScript:" + message.tabId, data); await Cache.getInstance().set("tabScript:" + message.tabId, data);
console.log("update menu");
this.updateScriptMenu();
} }
async unregisterMenuCommand({ id, uuid, tabId }: { id: number; uuid: string; tabId: number }) { async unregisterMenuCommand({ id, uuid, tabId }: { id: number; uuid: string; tabId: number }) {
@@ -83,7 +135,21 @@ export class PopupService {
if (script) { if (script) {
script.menus = script.menus.filter((item) => item.id !== id); script.menus = script.menus.filter((item) => item.id !== id);
} }
Cache.getInstance().set("tabScript:" + tabId, data); await Cache.getInstance().set("tabScript:" + tabId, data);
this.updateScriptMenu();
}
updateScriptMenu() {
// 获取当前页面并更新菜单
chrome.tabs.query({ active: true }, (tabs) => {
console.log("query", tabs);
if (!tabs.length) {
return;
}
const tab = tabs[0];
// 生成菜单
tab.id && this.genScriptMenu(tab.id);
});
} }
scriptToMenu(script: Script): ScriptMenu { scriptToMenu(script: Script): ScriptMenu {
@@ -225,15 +291,34 @@ export class PopupService {
}); });
} }
menuClick({ uuid, id, tabId, frameId }: { uuid: string; id: number; tabId: number; frameId: number }) { menuClick({
uuid,
id,
tabId,
frameId,
documentId,
}: {
uuid: string;
id: number;
tabId: number;
frameId: number;
documentId: string;
}) {
// 菜单点击事件 // 菜单点击事件
console.log("click menu", uuid, id, tabId); console.log("click menu", uuid, id, tabId, frameId, documentId);
this.runtime.sendMessageToTab(tabId, "menuClick", { this.runtime.sendMessageToTab(
uuid,
id,
tabId, tabId,
frameId, "menuClick",
}); {
uuid,
id,
tabId,
},
{
frameId,
documentId: documentId,
}
);
return Promise.resolve(true); return Promise.resolve(true);
} }
@@ -250,10 +335,47 @@ export class PopupService {
// 清理数据 // 清理数据
Cache.getInstance().del("tabScript:" + tabId); Cache.getInstance().del("tabScript:" + tabId);
}); });
// 监听页面切换加载菜单
chrome.tabs.onActivated.addListener((activeInfo) => {
this.genScriptMenu(activeInfo.tabId);
});
// 处理chrome菜单点击
chrome.contextMenus.onClicked.addListener(async (info, tab) => {
const menuIds = (info.menuItemId as string).split("_");
if (menuIds.length === 4) {
const [, , uuid, id] = menuIds;
// 寻找menu信息
const menu = await this.getScriptMenu(tab!.id!);
const script = menu.find((item) => item.uuid === uuid);
if (script) {
const menuItem = script.menus.find((item) => item.id === parseInt(id, 10));
if (menuItem) {
this.menuClick({
uuid: script.uuid,
id: menuItem.id,
tabId: tab!.id!,
frameId: menuItem.frameId || 0,
documentId: menuItem.documentId || "",
});
return;
}
}
}
});
// 监听运行次数 // 监听运行次数
this.mq.subscribe( this.mq.subscribe(
"pageLoad", "pageLoad",
async ({ tabId, frameId, scripts }: { tabId: number; frameId: number; scripts: ScriptMatchInfo[] }) => { async ({
tabId,
frameId,
scripts,
}: {
tabId: number;
frameId: number;
document: string;
scripts: ScriptMatchInfo[];
}) => {
this.addScriptRunNumber({ tabId, frameId, scripts }); this.addScriptRunNumber({ tabId, frameId, scripts });
// 设置角标和脚本 // 设置角标和脚本
chrome.action.getBadgeText( chrome.action.getBadgeText(

View File

@@ -122,12 +122,20 @@ export class RuntimeService {
} }
// 给指定tab发送消息 // 给指定tab发送消息
sendMessageToTab(tabId: number, action: string, data: any) { sendMessageToTab(
tabId: number,
action: string,
data: any,
options?: {
documentId?: string;
frameId?: number;
}
) {
if (tabId === -1) { 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), "content/runtime/" + action, data); return sendMessage(new ExtensionContentMessageSend(tabId, options), "content/runtime/" + action, data);
} }
async getPageScriptUuidByUrl(url: string) { async getPageScriptUuidByUrl(url: string) {

View File

@@ -30,6 +30,7 @@
"activeTab", "activeTab",
"webRequest", "webRequest",
"userScripts", "userScripts",
"contextMenus",
"declarativeNetRequest" "declarativeNetRequest"
], ],
"host_permissions": [ "host_permissions": [