Skip to content

Commit fa024d0

Browse files
authored
🐛 修复 Edge Android 移动端 popup 适配问题 (#686) (#1507)
两处移动端问题,根因不同分别修复: 1. popup 右侧留白:popup.html 把 html/body 宽度写死 320px。桌面端 popup 视口宽度恒等于 body 宽度(320px),移动端被强制撑满设备宽度(≥360px),固定 320px 导致右侧空白。改用媒体查询 @media (min-width:340px) 在移动端切换为 width:100%、max-height:none,桌面端不命中、行为零变化。 2. 点击「设置」等打不开内部页:window.open 在 Edge Android 上无法打开 chrome-extension:// 内部页(外部 https 网址正常)。将 6 处打开 /src/*.html 的 window.open 改为 openInCurrentTab(chrome.tabs.create):popup 的设置/新建 脚本、ScriptMenuList 的编辑/用户配置、MainLayout 拖拽导入、Tools 数据导入。 openInCurrentTab 增加返回创建出来的标签(chrome.tabs.Tab|undefined),供 MainLayout 判断是否成功打开;同步补充单元测试,并让 chrome-extension-mock 的 tabs.create 返回 Promise 以贴近真实 MV3。
1 parent 5478168 commit fa024d0

8 files changed

Lines changed: 63 additions & 23 deletions

File tree

packages/chrome-extension-mock/tab.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ export default class MockTab {
2626

2727
create(createProperties: chrome.tabs.CreateProperties, callback?: (tab: chrome.tabs.Tab) => void) {
2828
this.hook.emit("create", createProperties);
29-
callback?.({
30-
id: 1,
31-
} as chrome.tabs.Tab);
29+
const tab = { id: 1 } as chrome.tabs.Tab;
30+
callback?.(tab);
31+
return Promise.resolve(tab);
3232
}
3333

3434
remove(tabId: number) {

src/pages/components/ScriptMenuList/index.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import type {
3333
ScriptMenuItemOption,
3434
} from "@App/app/service/service_worker/types";
3535
import { popupClient, runtimeClient, scriptClient } from "@App/pages/store/features/script";
36+
import { openInCurrentTab } from "@App/pkg/utils/utils";
3637
import { i18nName } from "@App/locales/locales";
3738

3839
// 用于读取 metadata
@@ -245,8 +246,9 @@ const ListMenuItem = React.memo(
245246
className="tw-text-left"
246247
type="secondary"
247248
icon={<IconEdit />}
248-
onClick={() => {
249-
window.open(`/src/options.html#/script/editor/${item.uuid}`, "_blank");
249+
onClick={async () => {
250+
// 经由扩展 API 打开,兼容 Edge Android(移动端 window.open 打不开内部页,#686)
251+
await openInCurrentTab(`/src/options.html#/script/editor/${item.uuid}`);
250252
window.close();
251253
}}
252254
>
@@ -297,8 +299,9 @@ const ListMenuItem = React.memo(
297299
key="config"
298300
type="secondary"
299301
icon={<IconSettings />}
300-
onClick={() => {
301-
window.open(`/src/options.html#/?userConfig=${item.uuid}`, "_blank");
302+
onClick={async () => {
303+
// 经由扩展 API 打开,兼容 Edge Android(移动端 window.open 打不开内部页,#686)
304+
await openInCurrentTab(`/src/options.html#/?userConfig=${item.uuid}`);
302305
window.close();
303306
}}
304307
>

src/pages/components/layout/MainLayout.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import "./index.css";
3535
import { arcoLocale } from "@App/locales/arco";
3636
import { prepareScriptByCode } from "@App/pkg/utils/script";
3737
import { saveHandle } from "@App/pkg/utils/filehandle-db";
38-
import { makeBlobURL } from "@App/pkg/utils/utils";
38+
import { makeBlobURL, openInCurrentTab } from "@App/pkg/utils/utils";
3939
import ScrollBoundary from "@App/pages/components/layout/ScrollBoundary";
4040

4141
// --- 工具函数移出组件外,避免每次 Render 重新定义 ---
@@ -244,9 +244,9 @@ const MainLayout: React.FC<{
244244
}
245245
const fid = checkOk[1].value;
246246
await saveHandle(fid, fileHandle); // fileHandle以DB方式传送至安装页面
247-
// 打开安装页面
248-
const installWindow = window.open(`/src/install.html?file=${fid}`, "_blank");
249-
if (!installWindow) {
247+
// 打开安装页面(经由扩展 API,兼容 Edge Android —— 移动端 window.open 打不开内部页,#686)
248+
const installTab = await openInCurrentTab(`/src/install.html?file=${fid}`);
249+
if (!installTab) {
250250
throw new Error(t("install_page_open_failed"));
251251
}
252252
stat.success++;

src/pages/options/routes/Tools.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import { useSystemConfig } from "./utils";
2828
import { uuidv4 } from "@App/pkg/utils/uuid";
2929
import { cacheInstance } from "@App/app/cache";
3030
import { CACHE_KEY_IMPORT_FILE } from "@App/app/cache_key";
31-
import { makeBlobURL } from "@App/pkg/utils/utils";
31+
import { makeBlobURL, openInCurrentTab } from "@App/pkg/utils/utils";
3232

3333
const openImportWindow = async (filename: string, file: Blob) => {
3434
// 打开导入窗口,用cache实现数据交互
@@ -39,8 +39,8 @@ const openImportWindow = async (filename: string, file: Blob) => {
3939
filename: filename,
4040
url: url,
4141
});
42-
// 打开导入窗口,用cache实现数据交互
43-
window.open(chrome.runtime.getURL(`/src/import.html?uuid=${uuid}`), "_blank");
42+
// 打开导入窗口,用cache实现数据交互(经由扩展 API,兼容 Edge Android)
43+
await openInCurrentTab(`/src/import.html?uuid=${uuid}`);
4444
};
4545

4646
function Tools() {

src/pages/popup.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,16 @@
1515
min-height: 150px;
1616
max-height: 500px;
1717
}
18+
/* 桌面端 popup 的视口宽度恒等于 body 宽度(320px),永远不会命中此查询,行为不变;
19+
移动端(如 Edge Android)popup 被强制撑满设备宽度(≥360px),命中后填满外层容器,
20+
消除右侧留白(#686)。阈值取 340px:高于桌面 320px、低于最小手机宽度。 */
21+
@media (min-width: 340px) {
22+
html,
23+
body {
24+
width: 100%;
25+
max-height: none;
26+
}
27+
}
1828
</style>
1929
</head>
2030

src/pages/popup/App.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { popupClient, requestOpenBatchUpdatePage } from "@App/pages/store/featur
2222
import type { ScriptMenu, TPopupScript } from "@App/app/service/service_worker/types";
2323
import { systemConfig } from "@App/pages/store/global";
2424
import { isChineseUser, localePath } from "@App/locales/locales";
25-
import { getCurrentTab } from "@App/pkg/utils/utils";
25+
import { getCurrentTab, openInCurrentTab } from "@App/pkg/utils/utils";
2626
import { subscribeMessage } from "@App/pages/store/global";
2727
import type { TDeleteScript, TEnableScript, TScriptRunStatus } from "@App/app/service/queue";
2828
import { SCRIPT_RUN_STATUS_RUNNING } from "@App/app/repo/scripts";
@@ -309,8 +309,9 @@ function App() {
309309
systemConfig.setEnableScript(val);
310310
},
311311
handleSettingsClick: () => {
312-
// 使用 window.open 而非 <a> 连结:避免 Vivaldi 等浏览器偶发崩溃
313-
window.open("/src/options.html", "_blank");
312+
// 经由扩展 API 打开(而非 window.open / <a>):既避免 Vivaldi 偶发崩溃,
313+
// 也兼容 Edge Android —— 移动端 window.open 打不开 chrome-extension:// 内部页(#686)
314+
openInCurrentTab("/src/options.html");
314315
},
315316
handleNotificationClick: () => {
316317
setShowAlert((prev) => !prev);
@@ -341,7 +342,7 @@ function App() {
341342
await chrome.storage.local.set({
342343
activeTabUrl: { url: currentUrl },
343344
});
344-
window.open("/src/options.html#/script/editor?target=initial", "_blank");
345+
await openInCurrentTab("/src/options.html#/script/editor?target=initial");
345346
break;
346347
case "checkUpdate":
347348
requestOpenBatchUpdatePage(getUrlDomain(currentUrl));

src/pkg/utils/utils.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
cleanFileName,
66
formatBytes,
77
normalizeResponseHeaders,
8+
openInCurrentTab,
89
stringMatching,
910
stripUndefined,
1011
toCamelCase,
@@ -704,3 +705,29 @@ describe("stripUndefined", () => {
704705
expect(result).toEqual({ a: [1, 2, 3] });
705706
});
706707
});
708+
709+
describe("openInCurrentTab", () => {
710+
// 在 Edge Android 等移动端,window.open 打不开 chrome-extension:// 内部页,
711+
// 内部页必须经由扩展 API(chrome.tabs.create)打开(见 #686)。
712+
it("应通过 chrome.tabs.create 在当前标签页之后打开内部页", async () => {
713+
let created: chrome.tabs.CreateProperties | undefined;
714+
const onCreate = (props: chrome.tabs.CreateProperties) => {
715+
created = props;
716+
};
717+
(chrome.tabs as any).hook.on("create", onCreate);
718+
try {
719+
await openInCurrentTab("/src/options.html");
720+
} finally {
721+
(chrome.tabs as any).hook.removeListener("create", onCreate);
722+
}
723+
expect(created?.url).toBe("/src/options.html");
724+
// getCurrentTab 返回 index:0 的标签,新标签应排在其后
725+
expect(created?.index).toBe(1);
726+
});
727+
728+
// MainLayout 拖拽导入据返回值判断是否成功打开安装页,因此必须回传创建出来的标签
729+
it("应返回创建出来的标签供调用方判断是否成功打开", async () => {
730+
const tab = await openInCurrentTab("/src/install.html?file=abc");
731+
expect(tab?.id).toBe(1);
732+
});
733+
});

src/pkg/utils/utils.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ export async function getTab(tabId: number) {
111111
}
112112

113113
// 在当前页后打开一个新页面,如果指定tabId则在该tab后打开
114-
export async function openInCurrentTab(url: string, tabId?: number) {
114+
export async function openInCurrentTab(url: string, tabId?: number): Promise<chrome.tabs.Tab | undefined> {
115115
const tab = await (tabId ? getTab(tabId) : getCurrentTab());
116116
const createProperties: chrome.tabs.CreateProperties = { url };
117117
if (tab) {
@@ -128,20 +128,19 @@ export async function openInCurrentTab(url: string, tabId?: number) {
128128
}
129129
// 先尝试以 openerTabId 和 windowId 打开
130130
try {
131-
await chrome.tabs.create(createProperties);
132-
return;
131+
return await chrome.tabs.create(createProperties);
133132
} catch (e: any) {
134133
console.error("Error opening tab:", e);
135134
}
136135
// 失败的话,删去 openerTabId 和 windowId ,再次尝试打开
137136
delete createProperties.openerTabId;
138137
delete createProperties.windowId;
139138
try {
140-
await chrome.tabs.create(createProperties);
141-
return;
139+
return await chrome.tabs.create(createProperties);
142140
} catch (e: any) {
143141
console.error("Retry opeing tab error:", e);
144142
}
143+
return undefined;
145144
}
146145

147146
// 检查订阅规则是否改变,是否能够静默更新

0 commit comments

Comments
 (0)