Skip to content

Commit 3ecde9e

Browse files
cyfung1031CodFrm
andauthored
♻️ registerMenuCommand & unregisterMenuCommand 修正 (#826)
* registerMenuCommand & unregisterMenuCommand 修正 * // 增加一个 await Promise.reslove() 转移微任务队列 再判断长度是否为0 * 代码调整 * 代码调整 * 代码调整 * 代码调整 * 代码调整 * 代码调整 * 减少队列数量 * 减少消息传播数量 * 整理代码 --------- Co-authored-by: 王一之 <yz@ggnb.top>
1 parent 03da1ba commit 3ecde9e

3 files changed

Lines changed: 131 additions & 120 deletions

File tree

src/app/service/service_worker/popup.ts

Lines changed: 94 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ type TxUpdateScriptMenuCallback = (
2525
result: ScriptMenu[]
2626
) => Promise<ScriptMenu[] | undefined> | ScriptMenu[] | undefined;
2727

28+
const enum ScriptMenuRegisterType {
29+
REGISTER = 1,
30+
UNREGISTER = 2,
31+
}
32+
2833
// 以 tabId 为 key 的「执行次数」快取(字串形式存放),供 badge 显示使用。
2934
const runCountMap = new Map<number, string>();
3035

@@ -173,101 +178,105 @@ export class PopupService {
173178
}
174179

175180
// 防止并发导致频繁更新菜单,将注册菜单的请求集中在一个队列中处理
176-
registerMenuCommandMessages = new Map<string, TScriptMenuRegister[]>();
177-
178-
async registerMenuCommand(message: TScriptMenuRegister) {
179-
const { tabId, uuid } = message;
180-
const mrKey = `${tabId}.${uuid}`;
181-
if (!this.registerMenuCommandMessages.has(mrKey)) {
182-
this.registerMenuCommandMessages.set(mrKey, []);
183-
}
184-
this.registerMenuCommandMessages.get(mrKey)!.push(message);
185-
186-
let retUpdated = false;
187-
// 给脚本添加菜单
188-
await this.txUpdateScriptMenu(tabId, async (data) => {
189-
while (true) {
190-
const message = this.registerMenuCommandMessages.get(mrKey)?.shift();
191-
if (!message) {
192-
this.registerMenuCommandMessages.delete(mrKey);
193-
return data;
181+
updateMenuCommands = new Map<number, ((TScriptMenuRegister | TScriptMenuUnregister) & { registerType: number })[]>();
182+
isUpdateMenuDirty = false;
183+
184+
// 此函数必须是同步执行的,避免updateMenuCommands并发问题
185+
updateMenuCommand(tabId: number, data: ScriptMenu[]): string[] {
186+
const retUpdated = new Set<string>();
187+
const list = this.updateMenuCommands.get(tabId);
188+
if (!list) return [];
189+
const uuids = new Set(list.map((entry) => entry.uuid));
190+
const scripts = new Map(data.filter((item) => uuids.has(item.uuid)).map((item) => [item.uuid, item]));
191+
for (const listEntry of list) {
192+
const message = listEntry as TScriptMenuRegister;
193+
// message.key是唯一的。 即使在同一tab里的mainframe subframe也是不一样
194+
const { uuid, key, name } = message;
195+
const script = scripts.get(uuid);
196+
if (!script) continue;
197+
198+
if (listEntry.registerType === ScriptMenuRegisterType.REGISTER) {
199+
const menus = script.menus;
200+
retUpdated.add(script.uuid);
201+
// 以 options+name 生成稳定 groupKey:相同语义项目在 UI 只呈现一次,但可同时触发多个来源(frame)。
202+
// groupKey 用来表示「相同性质的项目」,允许重叠。
203+
// 例如 subframe 和 mainframe 创建了相同的 menu item,显示时只会出现一个。
204+
// 但点击后,两边都会执行。
205+
// 目的是整理显示,实际上内部还是存有多笔 entry(分别记录不同的 frameId 和 id)。
206+
const groupKey = uuidv5(
207+
message.options?.inputType
208+
? JSON.stringify({ ...message.options, autoClose: undefined, id: undefined, name: name })
209+
: `${name}\n${message.options?.accessKey || ""}`,
210+
groupKeyNS
211+
);
212+
const menu = menus.find((item) => item.key === key);
213+
if (!menu) {
214+
// 不存在新增
215+
menus.push({
216+
groupKey,
217+
key: key, // unique primary key
218+
name: name,
219+
options: message.options,
220+
tabId: tabId, // fix
221+
frameId: message.frameId, // fix with unique key
222+
documentId: message.documentId, // fix with unique key
223+
});
224+
} else {
225+
// 存在修改信息
226+
menu.name = message.name;
227+
menu.options = message.options;
228+
menu.groupKey = groupKey;
194229
}
195-
// message.key是唯一的。 即使在同一tab里的mainframe subframe也是不一样
196-
const { key, name, uuid } = message; // 唯一键, 项目显示名字, 脚本uuid
197-
const script = data.find((item) => item.uuid === uuid);
198-
if (script) {
199-
retUpdated = true;
200-
// 以 options+name 生成稳定 groupKey:相同语义项目在 UI 只呈现一次,但可同时触发多个来源(frame)。
201-
// groupKey 用来表示「相同性质的项目」,允许重叠。
202-
// 例如 subframe 和 mainframe 创建了相同的 menu item,显示时只会出现一个。
203-
// 但点击后,两边都会执行。
204-
// 目的是整理显示,实际上内部还是存有多笔 entry(分别记录不同的 frameId 和 id)。
205-
const groupKey = uuidv5(
206-
message.options?.inputType
207-
? JSON.stringify({ ...message.options, autoClose: undefined, id: undefined, name: name })
208-
: `${name}\n${message.options?.accessKey || ""}`,
209-
groupKeyNS
210-
);
211-
const menu = script.menus.find((item) => item.key === key);
212-
if (!menu) {
213-
// 不存在新增
214-
script.menus.push({
215-
groupKey,
216-
key: key, // unique primary key
217-
name: name,
218-
options: message.options,
219-
tabId: tabId, // fix
220-
frameId: message.frameId, // fix with unique key
221-
documentId: message.documentId, // fix with unique key
222-
});
223-
} else {
224-
// 存在修改信息
225-
menu.name = message.name;
226-
menu.options = message.options;
227-
menu.groupKey = groupKey;
228-
}
230+
} else if (listEntry.registerType === ScriptMenuRegisterType.UNREGISTER) {
231+
const menus = script.menus;
232+
// 删除菜单
233+
const index = menus.findIndex((item) => item.key === key);
234+
if (index >= 0) {
235+
retUpdated.add(uuid);
236+
menus.splice(index, 1);
229237
}
230238
}
231-
});
232-
if (retUpdated) {
233-
this.mq.publish<TPopupScript>("popupMenuRecordUpdated", { tabId, uuid });
234-
// 更新数据后再更新菜单
235-
await this.updateScriptMenu(tabId);
236239
}
240+
list.length = 0;
241+
this.updateMenuCommands.delete(tabId);
242+
return [...retUpdated];
237243
}
238244

239-
unregisterMenuCommandMessages = new Map<string, TScriptMenuUnregister[]>();
240-
241-
async unregisterMenuCommand({ key, uuid, tabId }: TScriptMenuUnregister) {
242-
const mrKey = `${tabId}.${uuid}`;
243-
if (!this.unregisterMenuCommandMessages.has(mrKey)) {
244-
this.unregisterMenuCommandMessages.set(mrKey, []);
245+
updateRegisterMenuCommand(
246+
message: TScriptMenuRegister | TScriptMenuUnregister,
247+
registerType: ScriptMenuRegisterType
248+
): Promise<void> {
249+
const { tabId } = message;
250+
let list = this.updateMenuCommands.get(tabId);
251+
if (!list) {
252+
this.updateMenuCommands.set(tabId, (list = []));
245253
}
246-
this.unregisterMenuCommandMessages.get(mrKey)!.push({ key, uuid, tabId });
247-
248-
let retUpdated = false;
249-
await this.txUpdateScriptMenu(tabId, async (data) => {
250-
while (true) {
251-
const message = this.unregisterMenuCommandMessages.get(mrKey)?.shift();
252-
if (!message) {
253-
this.unregisterMenuCommandMessages.delete(mrKey);
254-
return data;
254+
list.push({ ...message, registerType });
255+
let retUpdated: string[] | undefined;
256+
return Promise.resolve() // 增加一个 await Promise.reslove() 转移微任务队列 再判断长度是否为0
257+
.then(() => {
258+
if (list.length) {
259+
return this.txUpdateScriptMenu(tabId, (data) => {
260+
retUpdated = this.updateMenuCommand(tabId, data);
261+
return data;
262+
});
255263
}
256-
const script = data.find((item) => item.uuid === uuid);
257-
if (script) {
258-
retUpdated = true;
259-
// 删除菜单
260-
script.menus = script.menus.filter((item) => item.key !== key);
264+
})
265+
.then(() => {
266+
if (retUpdated?.length) {
267+
this.mq.publish<TPopupScript>("popupMenuRecordUpdated", { tabId, uuids: retUpdated });
268+
// 更新数据后再更新菜单
269+
this.updateScriptMenu(tabId);
261270
}
262-
return data;
263-
}
264-
});
271+
});
272+
}
265273

266-
if (retUpdated) {
267-
this.mq.publish<TPopupScript>("popupMenuRecordUpdated", { tabId, uuid });
268-
// 更新数据后再更新菜单
269-
await this.updateScriptMenu(tabId);
270-
}
274+
registerMenuCommand(message: TScriptMenuRegister) {
275+
this.updateRegisterMenuCommand(message, ScriptMenuRegisterType.REGISTER);
276+
}
277+
278+
unregisterMenuCommand({ key, uuid, tabId }: TScriptMenuUnregister) {
279+
this.updateRegisterMenuCommand({ key, uuid, tabId }, ScriptMenuRegisterType.UNREGISTER);
271280
}
272281

273282
async updateScriptMenu(tabId: number) {

src/app/service/service_worker/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,4 +253,4 @@ export type TBatchUpdateListAction =
253253
}[];
254254
};
255255

256-
export type TPopupScript = { tabId: number; uuid: string };
256+
export type TPopupScript = { tabId: number; uuids: string[] };

src/pages/popup/App.tsx

Lines changed: 36 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -102,44 +102,46 @@ function App() {
102102
);
103103
}),
104104

105-
subscribeMessage<TPopupScript>("popupMenuRecordUpdated", ({ tabId, uuid }: TPopupScript) => {
106-
// 仅处理当前页签(tab)的菜单更新,其他页签的变更忽略
107-
if (pageTabIdRef.current !== tabId) return;
108-
let url: string = "";
109-
// 透过 setState 回呼取得最新的 currentUrl(避免闭包读到旧值)
110-
setCurrentUrl((v) => {
111-
url = v || "";
112-
return v;
113-
});
114-
if (!url) return;
115-
popupClient.getPopupData({ url, tabId }).then((resp) => {
116-
if (!isMounted) return;
105+
subscribeMessage<TPopupScript>("popupMenuRecordUpdated", ({ tabId, uuids }: TPopupScript) => {
106+
for (const uuid of uuids) {
107+
// 仅处理当前页签(tab)的菜单更新,其他页签的变更忽略
108+
if (pageTabIdRef.current !== tabId) return;
109+
let url: string = "";
110+
// 透过 setState 回呼取得最新的 currentUrl(避免闭包读到旧值)
111+
setCurrentUrl((v) => {
112+
url = v || "";
113+
return v;
114+
});
115+
if (!url) return;
116+
popupClient.getPopupData({ url, tabId }).then((resp) => {
117+
if (!isMounted) return;
117118

118-
// 响应健全性检查:必须包含 scriptList,否则忽略此次更新
119-
if (!resp || !resp.scriptList) {
120-
console.warn("Invalid popup data response:", resp);
121-
return;
122-
}
119+
// 响应健全性检查:必须包含 scriptList,否则忽略此次更新
120+
if (!resp || !resp.scriptList) {
121+
console.warn("Invalid popup data response:", resp);
122+
return;
123+
}
123124

124-
// 仅抽取该 uuid 最新的 menus;仅更新 menus 栏位以维持其他属性的引用稳定
125-
const newMenus = resp.scriptList.find((item) => item.uuid === uuid)?.menus;
126-
if (!newMenus) return;
127-
setScriptList((prev) => {
128-
// 只针对 uuid 进行更新。其他项目保持参考一致
129-
const list = prev.map((item) => {
130-
return item.uuid !== uuid
131-
? item
132-
: {
133-
...item,
134-
menus: [...newMenus],
135-
menuUpdated: Date.now(),
136-
};
125+
// 仅抽取该 uuid 最新的 menus;仅更新 menus 栏位以维持其他属性的引用稳定
126+
const newMenus = resp.scriptList.find((item) => item.uuid === uuid)?.menus;
127+
if (!newMenus) return;
128+
setScriptList((prev) => {
129+
// 只针对 uuid 进行更新。其他项目保持参考一致
130+
const list = prev.map((item) => {
131+
return item.uuid !== uuid
132+
? item
133+
: {
134+
...item,
135+
menus: [...newMenus],
136+
menuUpdated: Date.now(),
137+
};
138+
});
139+
// 若 menus 数量变动,可能影响排序结果,因此需重新 sort
140+
list.sort(scriptListSorter);
141+
return list;
137142
});
138-
// 若 menus 数量变动,可能影响排序结果,因此需重新 sort
139-
list.sort(scriptListSorter);
140-
return list;
141143
});
142-
});
144+
}
143145
}),
144146
];
145147

0 commit comments

Comments
 (0)