feat: add right-click restorer for current site

- Add RightClickRestorer page with active unlock trigger
- Content script with dual-world strategy (isolated + main world)
- Monkey patch preventDefault/stopPropagation via background executeScript
- Overlay penetration for pointer-events blocking layers
- Delegated main-world injection to avoid CSP restrictions
- Async message handling for reliable state updates
- i18n translations (zh/en) and unit tests
This commit is contained in:
雨霖铃
2026-05-23 12:39:14 +08:00
parent 2d7bb8fc2c
commit fa0f1bdcad
16 changed files with 570 additions and 13 deletions
@@ -0,0 +1,59 @@
import { useCallback, useEffect, useState } from 'react';
import { MessageAction, sendMessageToContent } from '@/utils/messages';
export interface UseRightClickRestorerReturn {
domain: string;
isLoading: boolean;
isUnlocked: boolean;
unlock: () => Promise<void>;
}
export function useRightClickRestorer(): UseRightClickRestorerReturn {
const [domain, setDomain] = useState<string>('');
const [isLoading, setIsLoading] = useState<boolean>(true);
const [isUnlocked, setIsUnlocked] = useState<boolean>(false);
useEffect(() => {
const load = async () => {
try {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (tab?.url) {
try {
setDomain(new URL(tab.url).hostname);
} catch {
setDomain('');
}
}
const response = await sendMessageToContent(MessageAction.QUERY_RIGHT_CLICK_STATUS);
if (response?.success) {
setIsUnlocked(response.restored);
}
} catch (err) {
console.error('[RightClickRestorer] Failed to load state:', err);
} finally {
setIsLoading(false);
}
};
void load();
}, []);
const unlock = useCallback(async () => {
try {
const response = await sendMessageToContent(MessageAction.RESTORE_RIGHT_CLICK);
if (response?.success) {
setIsUnlocked(response.restored);
}
} catch (err) {
console.error('[RightClickRestorer] Failed to unlock:', err);
}
}, []);
return {
domain,
isLoading,
isUnlocked,
unlock,
};
}