Skip to content

Commit eb6dd93

Browse files
committed
🐛 区分网页安装标签来源
1 parent ebc6400 commit eb6dd93

4 files changed

Lines changed: 38 additions & 16 deletions

File tree

src/app/service/service_worker/client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export class ScriptClient extends Client {
5252

5353
// 获取安装信息
5454
getInstallInfo(uuid: string) {
55-
return this.do<[boolean, ScriptInfo, { byWebRequest?: boolean }]>("getInstallInfo", uuid);
55+
return this.do<[boolean, ScriptInfo, { byWebRequest?: boolean; openedInNewTab?: boolean }]>("getInstallInfo", uuid);
5656
}
5757

5858
install(params: TScriptInstallParam): Promise<TScriptInstallReturn> {

src/app/service/service_worker/script.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ export type TScriptInstallReturn = {
7474
updatetime: number | undefined; // 实际生效的更新时间(时间戳,毫秒)
7575
};
7676

77+
type InstallPageOptions = {
78+
source: InstallSource;
79+
byWebRequest?: boolean;
80+
openedInNewTab?: boolean;
81+
};
82+
7783
export type TRestoreResult = {
7884
restored: string[];
7985
conflicts: { uuid: string; name: string }[];
@@ -137,7 +143,7 @@ export class ScriptService {
137143
// 读取脚本url内容, 进行安装
138144
const logger = this.logger.with({ url: targetUrl });
139145
logger.debug("install script");
140-
this.openInstallPageByUrl(targetUrl, { source: "user", byWebRequest: true })
146+
this.openInstallPageByUrl(targetUrl, { source: "user", byWebRequest: true, openedInNewTab: true })
141147
.catch((e) => {
142148
logger.error("install script error", Logger.E(e));
143149
// 不再重定向当前url
@@ -361,7 +367,7 @@ export class ScriptService {
361367

362368
public async openInstallPageByUrl(
363369
url: string,
364-
options: { source: InstallSource; byWebRequest?: boolean }
370+
options: InstallPageOptions
365371
): Promise<{ success: boolean; msg: string }> {
366372
try {
367373
const installPageUrl = await this.getInstallPageUrl(url, options);
@@ -374,10 +380,7 @@ export class ScriptService {
374380
}
375381
}
376382

377-
public async getInstallPageUrl(
378-
url: string,
379-
options: { source: InstallSource; byWebRequest?: boolean }
380-
): Promise<string> {
383+
public async getInstallPageUrl(url: string, options: InstallPageOptions): Promise<string> {
381384
const uuid = uuidv4();
382385
try {
383386
await this.openUpdateOrInstallPage(uuid, url, options, false);
@@ -1166,7 +1169,7 @@ export class ScriptService {
11661169
async openUpdateOrInstallPage(
11671170
uuid: string,
11681171
url: string,
1169-
options: { source: InstallSource; byWebRequest?: boolean },
1172+
options: InstallPageOptions,
11701173
update: boolean,
11711174
logger?: Logger
11721175
) {

src/pages/install/useInstallData.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,21 @@ describe("useInstallData 数据流编排", () => {
304304
expect(closeSpy).not.toHaveBeenCalled();
305305
});
306306

307+
it("webNavigation 新开标签即使带 byWebRequest 且 history.length > 1 也应关闭", async () => {
308+
const result = await setupReady({ byWebRequest: true, openedInNewTab: true });
309+
const closeSpy = vi.spyOn(window, "close").mockImplementation(() => {});
310+
const backSpy = vi.spyOn(window.history, "back").mockImplementation(() => {});
311+
vi.spyOn(window.history, "length", "get").mockReturnValue(2);
312+
313+
await act(async () => {
314+
await result.current.install();
315+
await new Promise((r) => setTimeout(r, 320));
316+
});
317+
318+
expect(closeSpy).toHaveBeenCalledOnce();
319+
expect(backSpy).not.toHaveBeenCalled();
320+
});
321+
307322
it("byWebRequest 但 history.length 为 1 时应关闭无处可退的标签", async () => {
308323
const result = await setupReady({ byWebRequest: true });
309324
const closeSpy = vi.spyOn(window, "close").mockImplementation(() => {});

src/pages/install/useInstallData.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -125,18 +125,18 @@ const buildScriptInfo = (uuid: string, code: string, url: string, metadata: SCMe
125125
});
126126

127127
// 安装页可能是专为安装打开的新标签,也可能由 declarativeNetRequest 接管用户原标签。
128-
// 独立新标签可能继承多条历史,DNR 入口也可能没有上一页,因此必须同时检查入口与历史栈:
129-
// 仅在 DNR 接管且确实有历史可退时返回,否则关闭当前独立安装标签
128+
// webNavigation 入口同样带有 byWebRequest 标记,但它通过 chrome.tabs.create() 打开独立标签,
129+
// 因此必须额外保留 openedInNewTab 来源;只有真正接管用户原标签且确实有历史可退时才返回
130130
// install()/close() 等可能在短时间内被重复触发(如用户连续点击、close 与 install 的
131131
// setTimeout 前后脚打到),leaveInstallPageRunning 防止 back()/close() 被并发调用多次;
132132
// 推到 requestAnimationFrame 里执行,让触发它的那次交互(如按钮点击态)先完成一帧渲染。
133133
let leaveInstallPageRunning = false;
134-
const leaveInstallPage = (byWebRequest: boolean) => {
134+
const leaveInstallPage = (byWebRequest: boolean, openedInNewTab: boolean) => {
135135
if (leaveInstallPageRunning) return;
136136
leaveInstallPageRunning = true;
137137
requestAnimationFrame(() => {
138138
leaveInstallPageRunning = false;
139-
if (byWebRequest && window.history.length > 1) {
139+
if (byWebRequest && !openedInNewTab && window.history.length > 1) {
140140
window.history.back();
141141
} else {
142142
window.close();
@@ -191,6 +191,7 @@ export function useInstallData(): UseInstallData {
191191
const handleRef = useRef<FileSystemFileHandle | null>(null);
192192
const skillUuidRef = useRef<string | null>(null);
193193
const byWebRequestRef = useRef(false);
194+
const openedInNewTabRef = useRef(false);
194195

195196
useEffect(() => {
196197
const params = new URLSearchParams(location.search);
@@ -201,6 +202,7 @@ export function useInstallData(): UseInstallData {
201202
const urlIdx = location.search.indexOf("url=");
202203
const rawUrl = !uuid && urlIdx !== -1 ? location.search.slice(urlIdx + 4) : null;
203204
byWebRequestRef.current = params.get("byWebRequest") === "1";
205+
openedInNewTabRef.current = false;
204206
let cancelled = false;
205207

206208
const failed = (e: unknown) => {
@@ -267,6 +269,7 @@ export function useInstallData(): UseInstallData {
267269
if (code === undefined) throw new Error(t("install:script_info_load_failed"));
268270
info.code = code;
269271
byWebRequestRef.current = cached?.[2]?.byWebRequest === true;
272+
openedInNewTabRef.current = cached?.[2]?.openedInNewTab === true;
270273
await loadFromInfo(info, !!cached?.[0], cached?.[2] || {});
271274
} else if (rawUrl) {
272275
// .cat.md URL → Skill 安装流程(DNR 把 *.cat.md 重定向到安装页),不走脚本解析;仅 agent 启用时
@@ -371,7 +374,8 @@ export function useInstallData(): UseInstallData {
371374
await scriptClient.install({ script, code: info.code });
372375
notify.success(t("install:success"));
373376
}
374-
if (closeAfterInstall) setTimeout(() => leaveInstallPage(byWebRequestRef.current), 300);
377+
if (closeAfterInstall)
378+
setTimeout(() => leaveInstallPage(byWebRequestRef.current, openedInNewTabRef.current), 300);
375379
} catch (e) {
376380
notify.error(`${t("install:failed")}: ${(e as Error)?.message || String(e)}`);
377381
}
@@ -395,7 +399,7 @@ export function useInstallData(): UseInstallData {
395399
if (opts?.noMoreUpdates && info && !info.userSubscribe) {
396400
void scriptClient.setCheckUpdateUrl(info.uuid, false);
397401
}
398-
leaveInstallPage(byWebRequestRef.current);
402+
leaveInstallPage(byWebRequestRef.current, openedInNewTabRef.current);
399403
}, []);
400404

401405
// 监听文件变更后自动重装,并刷新视图代码
@@ -452,7 +456,7 @@ export function useInstallData(): UseInstallData {
452456
try {
453457
await agentClient.completeSkillInstall(uuid);
454458
notify.success(t("install:success"));
455-
setTimeout(() => leaveInstallPage(byWebRequestRef.current), 300);
459+
setTimeout(() => leaveInstallPage(byWebRequestRef.current, openedInNewTabRef.current), 300);
456460
} catch (e) {
457461
notify.error(`${t("install:failed")}: ${(e as Error)?.message || String(e)}`);
458462
}
@@ -461,7 +465,7 @@ export function useInstallData(): UseInstallData {
461465
const cancelSkill = useCallback(() => {
462466
const uuid = skillUuidRef.current;
463467
if (uuid) void agentClient.cancelSkillInstall(uuid);
464-
leaveInstallPage(byWebRequestRef.current);
468+
leaveInstallPage(byWebRequestRef.current, openedInNewTabRef.current);
465469
}, []);
466470

467471
// 重新触发加载(供加载失败后的重试按钮)

0 commit comments

Comments
 (0)