From ce17a2134f33a9b5cdc867781c98795377eeaa1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E4=B8=80=E4=B9=8B?= Date: Sat, 4 Apr 2026 12:24:48 +0800 Subject: [PATCH 1/8] =?UTF-8?q?=F0=9F=94=A7=20=E9=80=9A=E7=94=A8=E5=9F=BA?= =?UTF-8?q?=E7=A1=80=E8=AE=BE=E6=96=BD=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CI: build/test 流水线支持 feature/* 分支触发 - vitest: 修正 exclude 模式,确保子目录 node_modules/.claude 被排除 - message/server: SenderRuntime.getExtMessageSender 添加 null 检查, 防止 postMessage 通道(Offscreen→SW)因无 RuntimeMessageSender 崩溃 - message/window_message: connect() 使用 WindowPostMessage 包装, 确保 sandbox(origin:null)→offscreen 的消息不被丢弃 --- .github/workflows/build.yaml | 1 + .github/workflows/test.yaml | 1 + packages/message/server.ts | 9 +++++++++ packages/message/window_message.ts | 4 +++- vitest.config.ts | 2 +- 5 files changed, 15 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 3b0577d66..f7785c480 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -5,6 +5,7 @@ on: branches: - main - release/* + - feature/* - dev paths-ignore: - ".github/**" diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index e224d9d78..3537dad73 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -5,6 +5,7 @@ on: branches: - main - release/* + - feature/* - dev - develop/* pull_request: diff --git a/packages/message/server.ts b/packages/message/server.ts index 75e98081a..3df109f80 100644 --- a/packages/message/server.ts +++ b/packages/message/server.ts @@ -87,6 +87,15 @@ export class SenderRuntime { getExtMessageSender(): ExtMessageSender { const sender = this.sender as RuntimeMessageSender; + if (!sender) { + // postMessage 通道(如 Offscreen→SW)没有 RuntimeMessageSender + return { + windowId: -1, + tabId: -1, + frameId: undefined, + documentId: undefined, + }; + } return { windowId: sender.tab?.windowId || -1, // -1表示后台脚本 tabId: sender.tab?.id || -1, // -1表示后台脚本 diff --git a/packages/message/window_message.ts b/packages/message/window_message.ts index 44e89e863..c40f0d94f 100644 --- a/packages/message/window_message.ts +++ b/packages/message/window_message.ts @@ -100,7 +100,9 @@ export class WindowMessage implements Message { data, }; this.target.postMessage(body, "*"); - resolve(new WindowMessageConnect(body.messageId, this.EE, this.target)); + // 使用 WindowPostMessage 包装,确保后续 sendMessage 也带 "*" targetOrigin + // 否则沙箱(origin: null)→ offscreen(origin: chrome-extension://)的消息会被丢弃 + resolve(new WindowMessageConnect(body.messageId, this.EE, new WindowPostMessage(this.target))); }); } diff --git a/vitest.config.ts b/vitest.config.ts index 303e19862..73f07cf8e 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -25,7 +25,7 @@ export default defineConfig({ ], test: { environment: "jsdom", - exclude: ["e2e/**", "node_modules/**", ".claude/**"], + exclude: ["**/node_modules/**", "**/.claude/**", "e2e/**"], // List setup file setupFiles: ["./tests/vitest.setup.ts"], env: { From 665892d254449b8a7cc1b09eb68114904715b7cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E4=B8=80=E4=B9=8B?= Date: Sat, 4 Apr 2026 12:35:21 +0800 Subject: [PATCH 2/8] =?UTF-8?q?=F0=9F=90=9B=20=E9=80=9A=E7=94=A8=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E4=B8=8E=E4=BB=A3=E7=A0=81=E8=A7=84=E8=8C=83=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - confirm/App.tsx: 定时器从 setTimeout 递归改为 useEffect+setInterval, 修复内存泄漏和渲染循环;提取 PermissionConfirmRequest 组件 - utils.ts: sourceMapTo 添加 chrome.runtime?.getURL 防御, sandbox 环境中 chrome.runtime 不可用时降级为脚本名 - 5 处 eslint-disable react-hooks/exhaustive-deps 注释 - prettier 格式化:CSS/HTML/JSON 文件统一缩进和引号 - .prettierignore: 忽略 *.md 和 example/ - manifest.json: 纯格式化(prettier) - playwright.config.ts: 本地也启用 retry --- .prettierignore | 4 ++ playwright.config.ts | 2 +- src/assets/_locales/en/messages.json | 2 +- src/assets/_locales/zh_CN/messages.json | 2 +- src/index.css | 38 +++++++++--------- src/manifest.json | 27 ++++--------- src/pages/batchupdate/index.css | 16 ++++---- .../components/CloudScriptPlan/index.tsx | 1 + src/pages/components/ScriptSetting/Match.tsx | 1 + src/pages/components/layout/index.css | 16 ++++---- src/pages/confirm/App.tsx | 39 +++++++++++++------ src/pages/options/index.css | 4 +- src/pages/options/routes/Logger.tsx | 2 + src/pages/options/routes/ScriptList/index.tsx | 1 + .../options/routes/script/ScriptEditor.tsx | 1 + src/pages/options/routes/script/index.css | 22 +++++------ src/pages/sandbox.html | 2 +- src/pages/template.html | 2 +- src/pkg/utils/utils.ts | 3 +- 19 files changed, 98 insertions(+), 87 deletions(-) diff --git a/.prettierignore b/.prettierignore index 4a499ff0a..b6f0a56a2 100644 --- a/.prettierignore +++ b/.prettierignore @@ -5,3 +5,7 @@ yarn.lock # Claude Code .claude + +# Docs & examples +*.md +example/ diff --git a/playwright.config.ts b/playwright.config.ts index 9a15dabba..a5bea295d 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -8,7 +8,7 @@ export default defineConfig({ }, fullyParallel: false, forbidOnly: !!process.env.CI, - retries: process.env.CI ? 1 : 0, + retries: process.env.CI ? 1 : 1, workers: 1, reporter: process.env.CI ? [["html", { open: "never" }], ["list"]] : "list", outputDir: "test-results", diff --git a/src/assets/_locales/en/messages.json b/src/assets/_locales/en/messages.json index 4165381cb..07481b4e3 100644 --- a/src/assets/_locales/en/messages.json +++ b/src/assets/_locales/en/messages.json @@ -11,4 +11,4 @@ "scriptcat_description": { "message": "Everything can be scripted, allowing your browser to do more!" } -} \ No newline at end of file +} diff --git a/src/assets/_locales/zh_CN/messages.json b/src/assets/_locales/zh_CN/messages.json index 0b7d5775f..a4488bbdf 100644 --- a/src/assets/_locales/zh_CN/messages.json +++ b/src/assets/_locales/zh_CN/messages.json @@ -11,4 +11,4 @@ "scriptcat_description": { "message": "万物皆可脚本化,让你的浏览器可以做更多的事情!" } -} \ No newline at end of file +} diff --git a/src/index.css b/src/index.css index 6eb4944b4..6c46ac7e4 100644 --- a/src/index.css +++ b/src/index.css @@ -3,34 +3,34 @@ @unocss; body { - scrollbar-color: var(--color-scrollbar-thumb) var(--color-scrollbar-track); - /* 对于webkit浏览器的滚动条样式 */ - scrollbar-width: thin; + scrollbar-color: var(--color-scrollbar-thumb) var(--color-scrollbar-track); + /* 对于webkit浏览器的滚动条样式 */ + scrollbar-width: thin; } -body[arco-theme='dark'] { - --color-scrollbar-thumb: #6b6b6b; - --color-scrollbar-track: #2d2d2d; - --color-scrollbar-thumb-hover: #8c8c8c; +body[arco-theme="dark"] { + --color-scrollbar-thumb: #6b6b6b; + --color-scrollbar-track: #2d2d2d; + --color-scrollbar-thumb-hover: #8c8c8c; } -body[arco-theme='light'] { - --color-scrollbar-thumb: #6b6b6b; - --color-scrollbar-track: #f0f0f0; - --color-scrollbar-thumb-hover: #8c8c8c; +body[arco-theme="light"] { + --color-scrollbar-thumb: #6b6b6b; + --color-scrollbar-track: #f0f0f0; + --color-scrollbar-thumb-hover: #8c8c8c; } :root { - --sc-zero-1: 0; - --sc-zero-2: 0; - --sc-zero-3: 0; - --sc-zero-4: 0; + --sc-zero-1: 0; + --sc-zero-2: 0; + --sc-zero-3: 0; + --sc-zero-4: 0; } /* 自定义 .sc-inset-0 避免打包成 inset: 0 使旧浏览器布局错位 */ .sc-inset-0 { - top: var(--sc-zero-1); - left: var(--sc-zero-2); - right: var(--sc-zero-3); - bottom: var(--sc-zero-4); + top: var(--sc-zero-1); + left: var(--sc-zero-2); + right: var(--sc-zero-3); + bottom: var(--sc-zero-4); } diff --git a/src/manifest.json b/src/manifest.json index d55d8d80b..6b1fb8c30 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -10,9 +10,7 @@ }, "background": { "service_worker": "src/service_worker.js", - "scripts": [ - "src/service_worker.js" - ] + "scripts": ["src/service_worker.js"] }, "incognito": "split", "action": { @@ -43,26 +41,15 @@ "unlimitedStorage", "declarativeNetRequest" ], - "optional_permissions": [ - "background", - "userScripts" - ], - "host_permissions": [ - "" - ], + "optional_permissions": ["background", "userScripts"], + "host_permissions": [""], "sandbox": { - "pages": [ - "src/sandbox.html" - ] + "pages": ["src/sandbox.html"] }, "web_accessible_resources": [ { - "resources": [ - "/src/install.html" - ], - "matches": [ - "" - ] + "resources": ["/src/install.html"], + "matches": [""] } ] -} \ No newline at end of file +} diff --git a/src/pages/batchupdate/index.css b/src/pages/batchupdate/index.css index 6954b0a75..ba648c31d 100644 --- a/src/pages/batchupdate/index.css +++ b/src/pages/batchupdate/index.css @@ -1,19 +1,19 @@ .batchupdate-mainlayout { - min-height: max-content; - padding-top: 12px; - padding-bottom: 12px; + min-height: max-content; + padding-top: 12px; + padding-bottom: 12px; } body .text-clickable { - color: inherit; - cursor: pointer; + color: inherit; + cursor: pointer; } body .text-clickable:hover { - color: inherit; - text-decoration: underline; + color: inherit; + text-decoration: underline; } .script-card.card-disabled { - filter: contrast(0.9); + filter: contrast(0.9); } diff --git a/src/pages/components/CloudScriptPlan/index.tsx b/src/pages/components/CloudScriptPlan/index.tsx index c74710b88..50fe8c930 100644 --- a/src/pages/components/CloudScriptPlan/index.tsx +++ b/src/pages/components/CloudScriptPlan/index.tsx @@ -57,6 +57,7 @@ const CloudScriptPlan: React.FC<{ } }); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [script]); return ( { refreshMatch(); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [script]); const columns: ColumnProps[] = [ diff --git a/src/pages/components/layout/index.css b/src/pages/components/layout/index.css index fd53b2633..75764a6af 100644 --- a/src/pages/components/layout/index.css +++ b/src/pages/components/layout/index.css @@ -1,26 +1,26 @@ .arco-dropdown-menu-selected { - background-color: var(--color-fill-2) !important; + background-color: var(--color-fill-2) !important; } .action-tools .arco-dropdown-popup-visible .arco-icon-down { - transform: rotate(180deg); + transform: rotate(180deg); } -.action-tools>.arco-btn { - padding: 0 8px; +.action-tools > .arco-btn { + padding: 0 8px; } .arco-dropdown-menu-item, .arco-dropdown-menu-item a { - display: flex; - align-items: center; + display: flex; + align-items: center; } :is(.arco-dropdown-menu-pop-header, .arco-dropdown-menu-item, .arco-dropdown-menu-item a) > svg { - margin-right: .5em; + margin-right: 0.5em; } /* 避免拖拽时 tooltip 等元件弹出 */ .dragzone-active .arco-layout-content { - pointer-events: none; + pointer-events: none; } diff --git a/src/pages/confirm/App.tsx b/src/pages/confirm/App.tsx index b440a33ad..7e7f32e9d 100644 --- a/src/pages/confirm/App.tsx +++ b/src/pages/confirm/App.tsx @@ -4,24 +4,29 @@ import React, { useEffect, useMemo } from "react"; import { useTranslation } from "react-i18next"; import { permissionClient } from "../store/features/script"; -function App() { - const uuid = new URLSearchParams(location.search).get("uuid"); +// 权限确认组件 +function PermissionConfirmRequest({ uuid }: { uuid: string }) { const [confirm, setConfirm] = React.useState(); const [likeNum, setLikeNum] = React.useState(0); const [second, setSecond] = React.useState(30); const { t } = useTranslation(); - if (second === 0) { - window.close(); - } - - setTimeout(() => { - setSecond(second - 1); - }, 1000); + useEffect(() => { + const timer = setInterval(() => { + setSecond((s) => { + if (s <= 1) { + clearInterval(timer); + window.close(); + return 0; + } + return s - 1; + }); + }, 1000); + return () => clearInterval(timer); + }, []); useEffect(() => { - if (!uuid) return; window.addEventListener("beforeunload", () => { permissionClient.confirm(uuid, { allow: false, @@ -39,11 +44,10 @@ function App() { .catch((e: any) => { Message.error(e.message || t("get_confirm_error")); }); - }, []); + }, [uuid, t]); const handleConfirm = (allow: boolean, type: number) => { return async () => { - if (!uuid) return; try { await permissionClient.confirm(uuid, { allow, @@ -143,4 +147,15 @@ function App() { ); } +function App() { + const params = new URLSearchParams(location.search); + const uuid = params.get("uuid"); + + if (uuid) { + return ; + } + + return null; +} + export default App; diff --git a/src/pages/options/index.css b/src/pages/options/index.css index d7b98634f..492767d8c 100644 --- a/src/pages/options/index.css +++ b/src/pages/options/index.css @@ -79,7 +79,6 @@ h6.arco-typography { .arco-table-border .arco-table-th:first-child, .arco-table-border .arco-table-td:first-child { border-left: none !important; - } /* 卡片视图样式 */ @@ -107,5 +106,4 @@ h6.arco-typography { grid-template-columns: 1fr; } } - -} \ No newline at end of file +} diff --git a/src/pages/options/routes/Logger.tsx b/src/pages/options/routes/Logger.tsx index d77b5db94..b5e534733 100644 --- a/src/pages/options/routes/Logger.tsx +++ b/src/pages/options/routes/Logger.tsx @@ -84,6 +84,7 @@ function LoggerPage() { onQueryLog(); setInit(2); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [init]); useEffect(() => { @@ -119,6 +120,7 @@ function LoggerPage() { setInit(1); } }); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [startTime, endTime, refreshToken]); return ( diff --git a/src/pages/options/routes/ScriptList/index.tsx b/src/pages/options/routes/ScriptList/index.tsx index da178ee8a..96a969ff6 100644 --- a/src/pages/options/routes/ScriptList/index.tsx +++ b/src/pages/options/routes/ScriptList/index.tsx @@ -256,6 +256,7 @@ function ScriptList() { } }); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return ( diff --git a/src/pages/options/routes/script/ScriptEditor.tsx b/src/pages/options/routes/script/ScriptEditor.tsx index 2cb856004..ff85ffae9 100644 --- a/src/pages/options/routes/script/ScriptEditor.tsx +++ b/src/pages/options/routes/script/ScriptEditor.tsx @@ -80,6 +80,7 @@ const Editor: React.FC<{ }); callbackEditor(node.editor); return node.editor.dispose.bind(node.editor); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [node?.editor]); return ; diff --git a/src/pages/options/routes/script/index.css b/src/pages/options/routes/script/index.css index 387d8a2c2..9ecba1386 100644 --- a/src/pages/options/routes/script/index.css +++ b/src/pages/options/routes/script/index.css @@ -7,23 +7,23 @@ /* ScriptEditor 脚本列表颜色变量 */ body { - --editor-bg-selected: #D4D7DE; - --editor-bg-open: #EBEBEB; - --editor-bg-default: #F5F5F5; + --editor-bg-selected: #d4d7de; + --editor-bg-open: #ebebeb; + --editor-bg-default: #f5f5f5; } -body[arco-theme='dark'] { +body[arco-theme="dark"] { --editor-bg-selected: #414958; --editor-bg-open: #474747; --editor-bg-default: #333333; } .script-code-editor { - margin: 0; - padding: 0; - border: 0; - width: 100%; - height: 100%; - overflow: hidden; - box-sizing: border-box; + margin: 0; + padding: 0; + border: 0; + width: 100%; + height: 100%; + overflow: hidden; + box-sizing: border-box; } diff --git a/src/pages/sandbox.html b/src/pages/sandbox.html index 3761c576f..3f7c8b4c5 100644 --- a/src/pages/sandbox.html +++ b/src/pages/sandbox.html @@ -1,4 +1,4 @@ - + diff --git a/src/pages/template.html b/src/pages/template.html index 89a30c56d..6879472e2 100644 --- a/src/pages/template.html +++ b/src/pages/template.html @@ -1,4 +1,4 @@ - + diff --git a/src/pkg/utils/utils.ts b/src/pkg/utils/utils.ts index 71a891c14..40c3ba22c 100644 --- a/src/pkg/utils/utils.ts +++ b/src/pkg/utils/utils.ts @@ -456,7 +456,8 @@ export function cleanFileName(name: string): string { } export const sourceMapTo = (scriptName: string) => { - const url = chrome.runtime.getURL(`/${encodeURI(scriptName)}`); + // sandbox 环境中 chrome.runtime 不可用,使用脚本名作为 sourceURL + const url = chrome.runtime?.getURL ? chrome.runtime.getURL(`/${encodeURI(scriptName)}`) : encodeURI(scriptName); return `\n//# sourceURL=${url}`; }; From d50cee713318d17c3e25d123f9facfbab77a076e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E4=B8=80=E4=B9=8B?= Date: Sat, 4 Apr 2026 12:38:28 +0800 Subject: [PATCH 3/8] =?UTF-8?q?=E2=8F=AA=20=E6=92=A4=E5=9B=9E=20eslint-dis?= =?UTF-8?q?able=20=E6=B3=A8=E9=87=8A=E5=92=8C=20playwright=20retry=20?= =?UTF-8?q?=E6=94=B9=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除 6 处 react-hooks/exhaustive-deps 的 eslint-disable(仅 warn 无需抑制) - 还原 playwright retries 为 CI ? 1 : 0 --- playwright.config.ts | 2 +- src/pages/components/CloudScriptPlan/index.tsx | 1 - src/pages/components/ScriptSetting/Match.tsx | 1 - src/pages/options/routes/Logger.tsx | 2 -- src/pages/options/routes/ScriptList/index.tsx | 1 - src/pages/options/routes/script/ScriptEditor.tsx | 1 - 6 files changed, 1 insertion(+), 7 deletions(-) diff --git a/playwright.config.ts b/playwright.config.ts index a5bea295d..9a15dabba 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -8,7 +8,7 @@ export default defineConfig({ }, fullyParallel: false, forbidOnly: !!process.env.CI, - retries: process.env.CI ? 1 : 1, + retries: process.env.CI ? 1 : 0, workers: 1, reporter: process.env.CI ? [["html", { open: "never" }], ["list"]] : "list", outputDir: "test-results", diff --git a/src/pages/components/CloudScriptPlan/index.tsx b/src/pages/components/CloudScriptPlan/index.tsx index 50fe8c930..c74710b88 100644 --- a/src/pages/components/CloudScriptPlan/index.tsx +++ b/src/pages/components/CloudScriptPlan/index.tsx @@ -57,7 +57,6 @@ const CloudScriptPlan: React.FC<{ } }); } - // eslint-disable-next-line react-hooks/exhaustive-deps }, [script]); return ( { refreshMatch(); - // eslint-disable-next-line react-hooks/exhaustive-deps }, [script]); const columns: ColumnProps[] = [ diff --git a/src/pages/options/routes/Logger.tsx b/src/pages/options/routes/Logger.tsx index b5e534733..d77b5db94 100644 --- a/src/pages/options/routes/Logger.tsx +++ b/src/pages/options/routes/Logger.tsx @@ -84,7 +84,6 @@ function LoggerPage() { onQueryLog(); setInit(2); } - // eslint-disable-next-line react-hooks/exhaustive-deps }, [init]); useEffect(() => { @@ -120,7 +119,6 @@ function LoggerPage() { setInit(1); } }); - // eslint-disable-next-line react-hooks/exhaustive-deps }, [startTime, endTime, refreshToken]); return ( diff --git a/src/pages/options/routes/ScriptList/index.tsx b/src/pages/options/routes/ScriptList/index.tsx index 96a969ff6..da178ee8a 100644 --- a/src/pages/options/routes/ScriptList/index.tsx +++ b/src/pages/options/routes/ScriptList/index.tsx @@ -256,7 +256,6 @@ function ScriptList() { } }); } - // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return ( diff --git a/src/pages/options/routes/script/ScriptEditor.tsx b/src/pages/options/routes/script/ScriptEditor.tsx index ff85ffae9..2cb856004 100644 --- a/src/pages/options/routes/script/ScriptEditor.tsx +++ b/src/pages/options/routes/script/ScriptEditor.tsx @@ -80,7 +80,6 @@ const Editor: React.FC<{ }); callbackEditor(node.editor); return node.editor.dispose.bind(node.editor); - // eslint-disable-next-line react-hooks/exhaustive-deps }, [node?.editor]); return ; From 6c8a73e9dfa6fdfd55c5ed02803f2a58937fb734 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E4=B8=80=E4=B9=8B?= Date: Sat, 4 Apr 2026 12:41:29 +0800 Subject: [PATCH 4/8] =?UTF-8?q?=E2=8F=AA=20=E8=BF=98=E5=8E=9F=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E5=8C=96=E6=94=B9=E5=8A=A8=EF=BC=8C=E4=BF=9D=E6=8C=81?= =?UTF-8?q?=20release/v1.4=20=E5=8E=9F=E6=9C=89=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CSS/HTML/JSON/manifest 等文件还原为 release/v1.4 原始格式 --- src/assets/_locales/en/messages.json | 2 +- src/assets/_locales/zh_CN/messages.json | 2 +- src/index.css | 38 +++++++++++------------ src/manifest.json | 27 +++++++++++----- src/pages/batchupdate/index.css | 16 +++++----- src/pages/components/layout/index.css | 16 +++++----- src/pages/options/index.css | 4 ++- src/pages/options/routes/script/index.css | 22 ++++++------- src/pages/sandbox.html | 2 +- src/pages/template.html | 2 +- 10 files changed, 73 insertions(+), 58 deletions(-) diff --git a/src/assets/_locales/en/messages.json b/src/assets/_locales/en/messages.json index 07481b4e3..4165381cb 100644 --- a/src/assets/_locales/en/messages.json +++ b/src/assets/_locales/en/messages.json @@ -11,4 +11,4 @@ "scriptcat_description": { "message": "Everything can be scripted, allowing your browser to do more!" } -} +} \ No newline at end of file diff --git a/src/assets/_locales/zh_CN/messages.json b/src/assets/_locales/zh_CN/messages.json index a4488bbdf..0b7d5775f 100644 --- a/src/assets/_locales/zh_CN/messages.json +++ b/src/assets/_locales/zh_CN/messages.json @@ -11,4 +11,4 @@ "scriptcat_description": { "message": "万物皆可脚本化,让你的浏览器可以做更多的事情!" } -} +} \ No newline at end of file diff --git a/src/index.css b/src/index.css index 6c46ac7e4..6eb4944b4 100644 --- a/src/index.css +++ b/src/index.css @@ -3,34 +3,34 @@ @unocss; body { - scrollbar-color: var(--color-scrollbar-thumb) var(--color-scrollbar-track); - /* 对于webkit浏览器的滚动条样式 */ - scrollbar-width: thin; + scrollbar-color: var(--color-scrollbar-thumb) var(--color-scrollbar-track); + /* 对于webkit浏览器的滚动条样式 */ + scrollbar-width: thin; } -body[arco-theme="dark"] { - --color-scrollbar-thumb: #6b6b6b; - --color-scrollbar-track: #2d2d2d; - --color-scrollbar-thumb-hover: #8c8c8c; +body[arco-theme='dark'] { + --color-scrollbar-thumb: #6b6b6b; + --color-scrollbar-track: #2d2d2d; + --color-scrollbar-thumb-hover: #8c8c8c; } -body[arco-theme="light"] { - --color-scrollbar-thumb: #6b6b6b; - --color-scrollbar-track: #f0f0f0; - --color-scrollbar-thumb-hover: #8c8c8c; +body[arco-theme='light'] { + --color-scrollbar-thumb: #6b6b6b; + --color-scrollbar-track: #f0f0f0; + --color-scrollbar-thumb-hover: #8c8c8c; } :root { - --sc-zero-1: 0; - --sc-zero-2: 0; - --sc-zero-3: 0; - --sc-zero-4: 0; + --sc-zero-1: 0; + --sc-zero-2: 0; + --sc-zero-3: 0; + --sc-zero-4: 0; } /* 自定义 .sc-inset-0 避免打包成 inset: 0 使旧浏览器布局错位 */ .sc-inset-0 { - top: var(--sc-zero-1); - left: var(--sc-zero-2); - right: var(--sc-zero-3); - bottom: var(--sc-zero-4); + top: var(--sc-zero-1); + left: var(--sc-zero-2); + right: var(--sc-zero-3); + bottom: var(--sc-zero-4); } diff --git a/src/manifest.json b/src/manifest.json index 6b1fb8c30..d55d8d80b 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -10,7 +10,9 @@ }, "background": { "service_worker": "src/service_worker.js", - "scripts": ["src/service_worker.js"] + "scripts": [ + "src/service_worker.js" + ] }, "incognito": "split", "action": { @@ -41,15 +43,26 @@ "unlimitedStorage", "declarativeNetRequest" ], - "optional_permissions": ["background", "userScripts"], - "host_permissions": [""], + "optional_permissions": [ + "background", + "userScripts" + ], + "host_permissions": [ + "" + ], "sandbox": { - "pages": ["src/sandbox.html"] + "pages": [ + "src/sandbox.html" + ] }, "web_accessible_resources": [ { - "resources": ["/src/install.html"], - "matches": [""] + "resources": [ + "/src/install.html" + ], + "matches": [ + "" + ] } ] -} +} \ No newline at end of file diff --git a/src/pages/batchupdate/index.css b/src/pages/batchupdate/index.css index ba648c31d..6954b0a75 100644 --- a/src/pages/batchupdate/index.css +++ b/src/pages/batchupdate/index.css @@ -1,19 +1,19 @@ .batchupdate-mainlayout { - min-height: max-content; - padding-top: 12px; - padding-bottom: 12px; + min-height: max-content; + padding-top: 12px; + padding-bottom: 12px; } body .text-clickable { - color: inherit; - cursor: pointer; + color: inherit; + cursor: pointer; } body .text-clickable:hover { - color: inherit; - text-decoration: underline; + color: inherit; + text-decoration: underline; } .script-card.card-disabled { - filter: contrast(0.9); + filter: contrast(0.9); } diff --git a/src/pages/components/layout/index.css b/src/pages/components/layout/index.css index 75764a6af..fd53b2633 100644 --- a/src/pages/components/layout/index.css +++ b/src/pages/components/layout/index.css @@ -1,26 +1,26 @@ .arco-dropdown-menu-selected { - background-color: var(--color-fill-2) !important; + background-color: var(--color-fill-2) !important; } .action-tools .arco-dropdown-popup-visible .arco-icon-down { - transform: rotate(180deg); + transform: rotate(180deg); } -.action-tools > .arco-btn { - padding: 0 8px; +.action-tools>.arco-btn { + padding: 0 8px; } .arco-dropdown-menu-item, .arco-dropdown-menu-item a { - display: flex; - align-items: center; + display: flex; + align-items: center; } :is(.arco-dropdown-menu-pop-header, .arco-dropdown-menu-item, .arco-dropdown-menu-item a) > svg { - margin-right: 0.5em; + margin-right: .5em; } /* 避免拖拽时 tooltip 等元件弹出 */ .dragzone-active .arco-layout-content { - pointer-events: none; + pointer-events: none; } diff --git a/src/pages/options/index.css b/src/pages/options/index.css index 492767d8c..d7b98634f 100644 --- a/src/pages/options/index.css +++ b/src/pages/options/index.css @@ -79,6 +79,7 @@ h6.arco-typography { .arco-table-border .arco-table-th:first-child, .arco-table-border .arco-table-td:first-child { border-left: none !important; + } /* 卡片视图样式 */ @@ -106,4 +107,5 @@ h6.arco-typography { grid-template-columns: 1fr; } } -} + +} \ No newline at end of file diff --git a/src/pages/options/routes/script/index.css b/src/pages/options/routes/script/index.css index 9ecba1386..387d8a2c2 100644 --- a/src/pages/options/routes/script/index.css +++ b/src/pages/options/routes/script/index.css @@ -7,23 +7,23 @@ /* ScriptEditor 脚本列表颜色变量 */ body { - --editor-bg-selected: #d4d7de; - --editor-bg-open: #ebebeb; - --editor-bg-default: #f5f5f5; + --editor-bg-selected: #D4D7DE; + --editor-bg-open: #EBEBEB; + --editor-bg-default: #F5F5F5; } -body[arco-theme="dark"] { +body[arco-theme='dark'] { --editor-bg-selected: #414958; --editor-bg-open: #474747; --editor-bg-default: #333333; } .script-code-editor { - margin: 0; - padding: 0; - border: 0; - width: 100%; - height: 100%; - overflow: hidden; - box-sizing: border-box; + margin: 0; + padding: 0; + border: 0; + width: 100%; + height: 100%; + overflow: hidden; + box-sizing: border-box; } diff --git a/src/pages/sandbox.html b/src/pages/sandbox.html index 3f7c8b4c5..3761c576f 100644 --- a/src/pages/sandbox.html +++ b/src/pages/sandbox.html @@ -1,4 +1,4 @@ - + diff --git a/src/pages/template.html b/src/pages/template.html index 6879472e2..89a30c56d 100644 --- a/src/pages/template.html +++ b/src/pages/template.html @@ -1,4 +1,4 @@ - + From 7da1b896705a2d1e5afa8f2b5f9ce76bb1af530b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E4=B8=80=E4=B9=8B?= Date: Sat, 4 Apr 2026 12:52:55 +0800 Subject: [PATCH 5/8] =?UTF-8?q?=F0=9F=A7=AA=20=E8=A1=A5=E5=85=85=20message?= =?UTF-8?q?=20=E5=8C=85=E5=8D=95=E6=B5=8B=EF=BC=9Asender=20=E5=85=9C?= =?UTF-8?q?=E5=BA=95=20+=20connect=20targetOrigin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根据 PR #1328 Copilot 评审意见,补充两个单测: - SenderRuntime.getExtMessageSender() 在 sender 为 null/undefined/空对象时不崩溃并返回默认值 - WindowMessage.connect() 返回的连接 sendMessage 带 "*" targetOrigin --- packages/message/server.test.ts | 26 +++++++++++++++++ packages/message/window_message.test.ts | 39 ++++++++++++++++++++++++- 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/packages/message/server.test.ts b/packages/message/server.test.ts index f59b00abb..b58037491 100644 --- a/packages/message/server.test.ts +++ b/packages/message/server.test.ts @@ -540,6 +540,32 @@ describe("Server", () => { const extSender = capturedSender!.getExtMessageSender(); expect(extSender.tabId).toBe(-1); }); + + it.concurrent("sender 为 null/undefined 时不崩溃并返回默认值", async () => { + // postMessage 通道(如 Offscreen→SW)传入空对象作为 sender, + // SenderRuntime.getExtMessageSender() 应该返回默认兜底值 + const senderNull = new SenderRuntime(null as unknown as RuntimeMessageSender); + const extNull = senderNull.getExtMessageSender(); + expect(extNull.windowId).toBe(-1); + expect(extNull.tabId).toBe(-1); + expect(extNull.frameId).toBeUndefined(); + expect(extNull.documentId).toBeUndefined(); + + const senderUndefined = new SenderRuntime(undefined as unknown as RuntimeMessageSender); + const extUndefined = senderUndefined.getExtMessageSender(); + expect(extUndefined.windowId).toBe(-1); + expect(extUndefined.tabId).toBe(-1); + expect(extUndefined.frameId).toBeUndefined(); + expect(extUndefined.documentId).toBeUndefined(); + + // 空对象(ServiceWorkerMessageSend 实际传入的值)也应正常处理 + const senderEmpty = new SenderRuntime({} as RuntimeMessageSender); + const extEmpty = senderEmpty.getExtMessageSender(); + expect(extEmpty.windowId).toBe(-1); + expect(extEmpty.tabId).toBe(-1); + expect(extEmpty.frameId).toBeUndefined(); + expect(extEmpty.documentId).toBeUndefined(); + }); }); describe("Connect 功能测试", () => { diff --git a/packages/message/window_message.test.ts b/packages/message/window_message.test.ts index fcfed77f5..358e7f33f 100644 --- a/packages/message/window_message.test.ts +++ b/packages/message/window_message.test.ts @@ -1,5 +1,10 @@ import { describe, it, expect, beforeEach, vi, afterEach } from "vitest"; -import { ServiceWorkerMessageSend, ServiceWorkerClientMessage, type WindowMessageBody } from "./window_message"; +import { + ServiceWorkerMessageSend, + ServiceWorkerClientMessage, + WindowMessage, + type WindowMessageBody, +} from "./window_message"; import { Server } from "./server"; import type { MessageConnect } from "./types"; @@ -160,6 +165,38 @@ describe("ServiceWorkerClientMessage", () => { }); }); +describe("WindowMessage.connect", () => { + it("connect 返回的连接 sendMessage 应带 '*' targetOrigin", async () => { + // 模拟 target window,验证 postMessage 被调用时带 "*" + const targetPostMessage = vi.fn(); + const sourceWindow = { + addEventListener: vi.fn(), + } as unknown as Window; + const targetWindow = { + postMessage: targetPostMessage, + } as unknown as Window; + + const wm = new WindowMessage(sourceWindow, targetWindow); + + const con = await wm.connect({ action: "test/connect", data: "init" }); + + // connect() 本身会调用一次 postMessage(发送 connect 消息) + expect(targetPostMessage).toHaveBeenCalledTimes(1); + expect(targetPostMessage).toHaveBeenCalledWith(expect.objectContaining({ type: "connect" }), "*"); + + targetPostMessage.mockClear(); + + // 通过返回的连接发送消息,也应该带 "*" + con.sendMessage({ action: "test/msg", data: "hello" }); + + expect(targetPostMessage).toHaveBeenCalledTimes(1); + expect(targetPostMessage).toHaveBeenCalledWith( + expect.objectContaining({ type: "connectMessage", data: { action: "test/msg", data: "hello" } }), + "*" + ); + }); +}); + describe("ServiceWorkerMessageSend ↔ ServiceWorkerClientMessage 双向通信", () => { // 辅助函数: 将两端连接起来,模拟 postMessage 通道 function createWiredPair() { From 87d261195d8131cfeb3e616580714e8f1a91f02c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E4=B8=80=E4=B9=8B?= Date: Sun, 5 Apr 2026 11:25:40 +0800 Subject: [PATCH 6/8] =?UTF-8?q?=F0=9F=90=9B=20=E4=BF=AE=E5=A4=8D=E6=9D=83?= =?UTF-8?q?=E9=99=90=E7=A1=AE=E8=AE=A4=E9=A1=B5=20beforeunload=20=E7=9B=91?= =?UTF-8?q?=E5=90=AC=E5=99=A8=E6=B3=84=E6=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 拆分 useEffect:beforeunload 注册与 getPermissionInfo 请求独立 - 提取命名 handler,在 cleanup 中 removeEventListener - beforeunload effect 只依赖 [uuid],避免语言切换时重复注册 - .gitignore 忽略 .omc 本地开发目录 根据 PR #1328 @cyfung1031 评审意见处理 --- .gitignore | 1 + src/pages/confirm/App.tsx | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 143763ebb..aabce4a3c 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,4 @@ test-results playwright-report superpowers +.omc diff --git a/src/pages/confirm/App.tsx b/src/pages/confirm/App.tsx index 7e7f32e9d..7ad154ccf 100644 --- a/src/pages/confirm/App.tsx +++ b/src/pages/confirm/App.tsx @@ -27,13 +27,17 @@ function PermissionConfirmRequest({ uuid }: { uuid: string }) { }, []); useEffect(() => { - window.addEventListener("beforeunload", () => { + const handler = () => { permissionClient.confirm(uuid, { allow: false, type: 0, }); - }); + }; + window.addEventListener("beforeunload", handler, false); + return () => window.removeEventListener("beforeunload", handler, false); + }, [uuid]); + useEffect(() => { permissionClient .getPermissionInfo(uuid) .then((data) => { From 9f42b5b15338d3e323db18f4ad5faa3a09150c02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E4=B8=80=E4=B9=8B?= Date: Sun, 5 Apr 2026 11:52:12 +0800 Subject: [PATCH 7/8] =?UTF-8?q?=F0=9F=90=9B=20=E4=BF=AE=E5=A4=8D=20UnoCSS?= =?UTF-8?q?=20=E6=9A=97=E8=89=B2=E6=A8=A1=E5=BC=8F=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E8=BE=B9=E6=A1=86=E8=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 暗色模式下 UnoCSS 的 --un-default-border-color 默认使用浅色值, 导致 border-* 类的边框在暗色主题下与背景对比不足。 将其绑定到 Arco CSS 变量 --color-border-2,让边框色随主题自适应。 --- src/index.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.css b/src/index.css index 6eb4944b4..6c177117a 100644 --- a/src/index.css +++ b/src/index.css @@ -9,6 +9,7 @@ body { } body[arco-theme='dark'] { + --un-default-border-color: var(--color-border-2); --color-scrollbar-thumb: #6b6b6b; --color-scrollbar-track: #2d2d2d; --color-scrollbar-thumb-hover: #8c8c8c; From 0368bc12e94dbf17d33e2679e903961b2513d825 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E4=B8=80=E4=B9=8B?= Date: Sun, 5 Apr 2026 11:52:18 +0800 Subject: [PATCH 8/8] =?UTF-8?q?=F0=9F=A7=AA=20navigation=5Fhandle:=20?= =?UTF-8?q?=E7=94=A8=20resetAttachedForTest=20=E6=9B=BF=E4=BB=A3=20vi.rese?= =?UTF-8?q?tModules?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit vi.resetModules() 会清空全局模块缓存,导致后续测试重新加载 LoggerCore、 chrome mock 等模块时出错,在 CI 上表现为偶发失败。 改为导出 resetAttachedForTest() 重置模块级 attached 单例, 测试无需动态 import 即可验证多次调用的幂等性。 --- .../content/gm_api/navigation_handle.test.ts | 25 ++++--------------- .../content/gm_api/navigation_handle.ts | 5 ++++ 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/src/app/service/content/gm_api/navigation_handle.test.ts b/src/app/service/content/gm_api/navigation_handle.test.ts index c00740d47..29d7dfbdd 100644 --- a/src/app/service/content/gm_api/navigation_handle.test.ts +++ b/src/app/service/content/gm_api/navigation_handle.test.ts @@ -1,16 +1,5 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; -import { UrlChangeEvent } from "./navigation_handle.js"; - -// attachNavigateHandler 使用模块级 attached 单例,需要在每个测试前重置模块 -const importFresh = async () => { - vi.resetModules(); - // vi.resetModules() 会清空模块缓存,后续 import 得到全新的 LoggerCore 类, - // 需要重新初始化以免后续测试文件中 LoggerCore.getInstance() 返回 undefined - // @ts-expect-error 动态 import 路径别名在 tsc nodenext 下无法解析 - const { default: LC, EmptyWriter: EW } = await import("@App/app/logger/core"); - new LC({ level: "trace", consoleLevel: "trace", writer: new EW(), labels: { env: "test" } }); - return await import("./navigation_handle.js"); -}; +import { UrlChangeEvent, attachNavigateHandler, resetAttachedForTest } from "./navigation_handle"; describe("UrlChangeEvent", () => { it.concurrent("应包含 url 属性", () => { @@ -64,10 +53,10 @@ describe("attachNavigateHandler", () => { beforeEach(() => { vi.restoreAllMocks(); + resetAttachedForTest(); }); - it("不支持 Navigation API 时不应注册监听器", async () => { - const { attachNavigateHandler } = await importFresh(); + it("不支持 Navigation API 时不应注册监听器", () => { const win = { location: { href: "https://example.com/" } } as any; attachNavigateHandler(win); // 没有 navigation 属性,不应报错也不应标记为 attached @@ -77,16 +66,14 @@ describe("attachNavigateHandler", () => { expect(mock.win.navigation.addEventListener).toHaveBeenCalledWith("navigate", expect.any(Function), false); }); - it("应在 win.navigation 上注册 navigate 监听器", async () => { - const { attachNavigateHandler } = await importFresh(); + it("应在 win.navigation 上注册 navigate 监听器", () => { const mock = createMockWin(); attachNavigateHandler(mock.win); expect(mock.win.navigation.addEventListener).toHaveBeenCalledTimes(1); expect(mock.win.navigation.addEventListener).toHaveBeenCalledWith("navigate", expect.any(Function), false); }); - it("多次调用只注册一次", async () => { - const { attachNavigateHandler } = await importFresh(); + it("多次调用只注册一次", () => { const mock = createMockWin(); attachNavigateHandler(mock.win); attachNavigateHandler(mock.win); @@ -95,7 +82,6 @@ describe("attachNavigateHandler", () => { }); it("URL 变化时应派发 urlchange 事件", async () => { - const { attachNavigateHandler } = await importFresh(); const mock = createMockWin("https://example.com/"); attachNavigateHandler(mock.win); mock.fireNavigate("https://example.com/new"); @@ -109,7 +95,6 @@ describe("attachNavigateHandler", () => { }); it("URL 未变化时不应派发事件", async () => { - const { attachNavigateHandler } = await importFresh(); const mock = createMockWin("https://example.com/"); attachNavigateHandler(mock.win); // destination.url 与当前 href 相同 diff --git a/src/app/service/content/gm_api/navigation_handle.ts b/src/app/service/content/gm_api/navigation_handle.ts index 6cdcf8417..a536f6a70 100644 --- a/src/app/service/content/gm_api/navigation_handle.ts +++ b/src/app/service/content/gm_api/navigation_handle.ts @@ -10,6 +10,11 @@ export class UrlChangeEvent extends Event { let attached = false; +// 仅供测试使用,重置 attached 标记 +export const resetAttachedForTest = () => { + attached = false; +}; + const getPropGetter = (obj: T, key: keyof T) => { // 避免直接 obj[key] 读取。或会被 hack for (let t = obj; t; t = Native.objectGetPrototypeOf(t)) {