修复填入当前域名:错误提示层级与侧边栏取址

This commit is contained in:
yinjun.chen1
2026-09-24 13:58:45 +08:00
parent ca532a181d
commit 9f0e077748
6 changed files with 100 additions and 32 deletions
+2 -1
View File
@@ -966,7 +966,8 @@ h2 {
font-size: 13px;
opacity: 0;
transition: all 0.3s;
z-index: 9999;
/* 必须高于弹窗遮罩(.modal z-index: 10000),否则弹窗内操作触发的提示会被遮罩盖住 */
z-index: 10001;
pointer-events: none;
max-width: 90vw;
white-space: nowrap;
+37 -1
View File
@@ -5,8 +5,11 @@ let autoLockTimer: ReturnType<typeof setTimeout> | null = null;
const AUTO_LOCK_MS = 10 * 60 * 1000; // 10 分钟无操作自动锁定
chrome.runtime.onInstalled.addListener(() => {
// 关闭"点击图标即开面板"的内置行为,改由 action.onClicked 接管:
// 点击图标那一刻 Chrome 会授予 activeTab,onClicked 里趁机捕获当前标签页
// 地址缓存起来,侧边栏里"填入当前域名"在切换标签页后也能取到地址。
chrome.sidePanel
.setPanelBehavior({ openPanelOnActionClick: true })
.setPanelBehavior({ openPanelOnActionClick: false })
.catch((err) => console.warn("[AutoLogin] 设置 sidePanel 行为失败:", err));
migrateLegacyVault();
@@ -20,10 +23,43 @@ chrome.runtime.onStartup.addListener(() => {
chrome.action.onClicked.addListener((tab) => {
if (tab?.id !== undefined) {
// 点击图标的用户手势已授予 activeTab,此刻 tab.url 一定可读;
// 缓存地址供侧边栏后续使用(侧边栏持久的场景下切换标签页后权限会失效)
if (tab.url && /^https?:/.test(tab.url)) {
chrome.storage.session
.set({
[LAST_AUTHORIZED_TAB]: {
tabId: tab.id,
url: tab.url,
title: tab.title || "",
},
})
.catch((err) => console.warn("[AutoLogin] 缓存当前标签页地址失败:", err));
}
chrome.sidePanel.open({ tabId: tab.id }).catch(() => {});
}
});
// 标签页导航 / 关闭后,缓存的地址作废,避免把导航前的旧地址填进表单
const LAST_AUTHORIZED_TAB = "lastAuthorizedTab";
async function clearLastAuthorizedTab(tabId: number): Promise<void> {
try {
const data = await chrome.storage.session.get(LAST_AUTHORIZED_TAB);
const cached = data[LAST_AUTHORIZED_TAB] as { tabId?: number } | undefined;
if (cached?.tabId === tabId) {
await chrome.storage.session.remove(LAST_AUTHORIZED_TAB);
}
} catch {
// 缓存清理失败不影响主流程
}
}
chrome.tabs.onUpdated.addListener((tabId, changeInfo) => {
if (changeInfo.url) clearLastAuthorizedTab(tabId);
});
chrome.tabs.onRemoved.addListener((tabId) => clearLastAuthorizedTab(tabId));
// 旧版明文数据迁移:把 loginConfigs 转为 environments 明文结构
async function migrateLegacyVault() {
const data = await chrome.storage.local.get(["environments", "loginConfigs", "vaultMeta"]);
+6 -8
View File
@@ -6,6 +6,7 @@ import { t } from "../i18n.js";
import { resolveAlias, fetchFaviconByDomain } from "../ui-utils.js";
import type { DomainConfig } from "../types.js";
import { $ } from "./dom";
import { getActiveTabInfo } from "./current-tab";
import type { PopupCtx } from "./context";
const editConfigModal = $("editConfigModal");
@@ -129,19 +130,16 @@ export function initEditConfigModal(ctx: PopupCtx) {
}
});
// 填入当前标签页域名
// 填入当前标签页域名
editFillDomainBtn.addEventListener("click", async () => {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (!tab) {
ctx.showToast(t("toastNoTab"));
return;
}
if (!tab.url) {
// 侧边栏场景下 activeTab 授权不可靠,统一走 getActiveTabInfo 的缓存兜底
const info = await getActiveTabInfo();
if (!info) {
ctx.showToast(t("toastNoTabUrl"));
return;
}
try {
const hostname = new URL(tab.url).hostname;
const hostname = new URL(info.url).hostname;
if (!hostname) {
ctx.showToast(t("toastNoDomain"));
return;
+7 -10
View File
@@ -6,6 +6,7 @@ import { t } from "../i18n.js";
import { resolveAlias, fetchFaviconByDomain } from "../ui-utils.js";
import type { DomainConfig } from "../types.js";
import { $ } from "./dom";
import { getActiveTabInfo } from "./current-tab";
import type { PopupCtx } from "./context";
const domainInput = $("domain");
@@ -135,20 +136,16 @@ export function initConfigForm(ctx: PopupCtx) {
ctx.refreshEnvManage();
});
// 填入当前标签页域名
// 填入当前标签页域名
fillDomainBtn.addEventListener("click", async () => {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (!tab) {
ctx.showToast(t("toastNoTab"));
return;
}
if (!tab.url) {
// 未授权时读不到地址:提示用户点扩展图标重新授予 activeTab
// 侧边栏场景下 activeTab 授权不可靠,统一走 getActiveTabInfo 的缓存兜底
const info = await getActiveTabInfo();
if (!info) {
ctx.showToast(t("toastNoTabUrl"));
return;
}
try {
const u = new URL(tab.url);
const u = new URL(info.url);
const host = u.host;
if (!host) {
ctx.showToast(t("toastNoDomain"));
@@ -156,7 +153,7 @@ export function initConfigForm(ctx: PopupCtx) {
}
const value = u.pathname && u.pathname !== "/" ? host + u.pathname : host;
domainInput.value = value;
if (tab.title) aliasInput.value = tab.title;
if (info.title) aliasInput.value = info.title;
saveDraft();
ctx.showToast(t("toastDomainFilled", value));
} catch {
+38
View File
@@ -0,0 +1,38 @@
/**
* 取当前活动标签页的 URL / 标题,供"填入当前域名"等按钮使用。
*
* 本扩展以侧边栏(side panel)承载 popup.html,而 activeTab 是临时权限:
* 只有用户点击浏览器工具栏的扩展图标那一瞬间才会授予"当时激活的标签页",
* 切换标签页或页面导航后权限即失效,chrome.tabs.query 返回的 tab.url 会是
* undefined(扩展也没有 tabs / 站点 host 权限时读不到地址)。
*
* 因此取址按两级兜底:
* 1. 直接读 tab.url —— 刚点过扩展图标、或该站点已授权 host 权限时可用;
* 2. 读 background 在"点击图标授权瞬间"缓存到 session storage 的
* lastAuthorizedTab —— 仅当缓存的 tabId 与当前活动标签页一致才使用,
* 防止把别的标签页/导航前的旧地址填进表单。
*/
export interface ActiveTabInfo {
/** 当前活动标签页 id(用于注入 content script 等场景) */
tabId: number;
url: string;
title?: string;
}
export const LAST_AUTHORIZED_TAB = "lastAuthorizedTab";
export async function getActiveTabInfo(): Promise<ActiveTabInfo | null> {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (!tab || tab.id === undefined) return null;
if (tab.url) return { tabId: tab.id, url: tab.url, title: tab.title };
// 直接读不到:尝试 background 在授权瞬间缓存的地址
const data = await chrome.storage.session.get(LAST_AUTHORIZED_TAB);
const cached = data[LAST_AUTHORIZED_TAB] as
| { tabId?: number; url?: string; title?: string }
| undefined;
if (cached && cached.tabId === tab.id && cached.url) {
return { tabId: tab.id, url: cached.url, title: cached.title };
}
return null;
}
+10 -12
View File
@@ -11,6 +11,7 @@
*/
import { t } from "../i18n.js";
import { $ } from "./dom";
import { getActiveTabInfo } from "./current-tab";
import type { PopupCtx } from "./context";
/** content script 已注入的标记,定义在 content.ts 顶部 */
@@ -73,20 +74,17 @@ export function initFillCurrent(ctx: PopupCtx) {
const fillCurrentBtn = $("fillCurrent");
fillCurrentBtn.addEventListener("click", async () => {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (!tab || tab.id === undefined) {
ctx.showToast(t("toastNoTab"));
return;
}
if (!tab.url) {
// 该标签页没有 activeTab 授权(例如切换标签页后侧边栏仍开着)
// 侧边栏场景下 activeTab 授权不可靠,统一走 getActiveTabInfo 的缓存兜底
const info = await getActiveTabInfo();
if (!info) {
ctx.showToast(t("toastNoTabUrl"));
return;
}
const tabId = info.tabId;
// 跨所有环境查找匹配当前标签页的配置
const allConfigs = await EnvStore.loadAllConfigs();
const matched = allConfigs.filter((c) => isDomainMatch(tab.url, c.domain));
const matched = allConfigs.filter((c) => isDomainMatch(info.url, c.domain));
if (matched.length === 0) {
ctx.showToast(t("toastNoMatch"));
@@ -95,8 +93,8 @@ export function initFillCurrent(ctx: PopupCtx) {
if (matched.length === 1) {
try {
const frameId = await ensureContentScript(tab.id);
const resp: any = await sendToTab(tab.id, { action: "manualFill", config: matched[0] }, frameId);
const frameId = await ensureContentScript(tabId);
const resp: any = await sendToTab(tabId, { action: "manualFill", config: matched[0] }, frameId);
if (resp && (resp as any).success) {
ctx.showToast(t("toastFilled"));
} else if (resp && resp.diag) {
@@ -112,8 +110,8 @@ export function initFillCurrent(ctx: PopupCtx) {
}
try {
const frameId = await ensureContentScript(tab.id);
await sendToTab(tab.id, { action: "manualFillMulti", configs: matched, tabUrl: tab.url }, frameId);
const frameId = await ensureContentScript(tabId);
await sendToTab(tabId, { action: "manualFillMulti", configs: matched, tabUrl: info.url }, frameId);
ctx.showToast(t("toastMultiMatch", String(matched.length)));
} catch (err) {
toastSendError(ctx, err);