Skip to content

Commit 61292fc

Browse files
committed
Enhance state management and message handling in content scripts
- Implemented state restoration from storage in the background script to ensure persistent diagnostic mode across browser restarts. - Added message listener in the content script to handle updates from the background, improving real-time state synchronization. - Updated Dashboard component to include the missing 'inspecting' state for better UI feedback. - Enhanced README documentation with details on recent fixes and testing procedures.
1 parent 5142893 commit 61292fc

File tree

7 files changed

+180
-21
lines changed

7 files changed

+180
-21
lines changed

.cursor/rules/agent.mdc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22
alwaysApply: true
33
---
44

5-
- 当你作为 agent 时, 再帮我写完代码后, 不要跑任何 pnpm 命令
5+
- 不要自动跑任何 pnpm 命令
6+
- 不要自动书写 README.md 文件

chrome-extension/src/background/index.ts

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import 'webextension-polyfill';
22
import { startListenTabs } from './tabs';
33
import { ignoreHref } from '@extension/shared';
4-
import { exampleThemeStorage, translationModeStorage } from '@extension/storage';
4+
import { exampleThemeStorage, translationModeStorage, contentUIStateStorage } from '@extension/storage';
55
import type { AllMessage, QueryResponse, State } from '@extension/shared';
66

77
console.log('Background loaded');
@@ -24,6 +24,7 @@ let isConnecting = false;
2424

2525
const waitingQuery: { [key: string]: { resolves: [(value: AllMessage) => void] } } = {};
2626

27+
// 初始化状态,从 storage 中恢复
2728
const state: State = {
2829
interactionMode: 'full',
2930
demoMode: false,
@@ -33,6 +34,29 @@ const state: State = {
3334
inspecting: false,
3435
};
3536

37+
// 从 storage 中恢复状态
38+
const initializeStateFromStorage = async () => {
39+
try {
40+
console.log('background: 从 storage 中恢复状态');
41+
const storedState = await contentUIStateStorage.get();
42+
console.log('background: 恢复的状态', storedState);
43+
44+
// 更新状态,但保持 running 状态为 false(需要 WebSocket 连接)
45+
state.interactionMode = storedState.interactionMode;
46+
state.demoMode = storedState.demoMode;
47+
state.inspecting = storedState.inspecting;
48+
state.ignored = storedState.ignored;
49+
state.running = false; // 初始时设为 false,等 WebSocket 连接成功后再设为 true
50+
51+
console.log('background: 状态已恢复', state);
52+
} catch (error) {
53+
console.error('background: 恢复状态失败', error);
54+
}
55+
};
56+
57+
// 在 background script 启动时恢复状态
58+
initializeStateFromStorage();
59+
3660
const syncStateToContent = async () => {
3761
const tabs = await chrome.tabs.query({});
3862
tabs.forEach(tab => {
@@ -83,9 +107,21 @@ const listenMessageForUI = (
83107
}
84108
case 'SetState': {
85109
const { interactionMode, demoMode, inspecting } = message;
110+
console.log('background: 收到 SetState', { interactionMode, demoMode, inspecting });
111+
86112
state.interactionMode = interactionMode;
87113
state.demoMode = demoMode;
88114
state.inspecting = inspecting;
115+
116+
// 同步到 storage
117+
contentUIStateStorage.updateGlobalState({
118+
running: state.running,
119+
ignored: state.ignored,
120+
interactionMode,
121+
demoMode,
122+
inspecting,
123+
});
124+
89125
syncStateToContent();
90126
return false;
91127
}
@@ -132,6 +168,16 @@ const connectWebSocket = () => {
132168
isConnecting = false;
133169
ws?.send(JSON.stringify({ type: 'ping' }));
134170
state.running = true;
171+
172+
// 更新 storage 中的 running 状态
173+
contentUIStateStorage.updateGlobalState({
174+
running: true,
175+
ignored: state.ignored,
176+
interactionMode: state.interactionMode,
177+
demoMode: state.demoMode,
178+
inspecting: state.inspecting,
179+
});
180+
135181
syncStateToContent();
136182
};
137183

@@ -152,6 +198,16 @@ const connectWebSocket = () => {
152198
console.error('❌ WebSocket 错误:', err);
153199
ws?.close(); // 主动触发 onclose
154200
state.running = false;
201+
202+
// 更新 storage 中的 running 状态
203+
contentUIStateStorage.updateGlobalState({
204+
running: false,
205+
ignored: state.ignored,
206+
interactionMode: state.interactionMode,
207+
demoMode: state.demoMode,
208+
inspecting: state.inspecting,
209+
});
210+
155211
syncStateToContent();
156212
};
157213

@@ -160,6 +216,16 @@ const connectWebSocket = () => {
160216
isConnecting = false;
161217
ws = null;
162218
state.running = false;
219+
220+
// 更新 storage 中的 running 状态
221+
contentUIStateStorage.updateGlobalState({
222+
running: false,
223+
ignored: state.ignored,
224+
interactionMode: state.interactionMode,
225+
demoMode: state.demoMode,
226+
inspecting: state.inspecting,
227+
});
228+
163229
syncStateToContent();
164230
};
165231

@@ -169,6 +235,16 @@ const connectWebSocket = () => {
169235
isConnecting = false;
170236
success = false;
171237
state.running = false;
238+
239+
// 更新 storage 中的 running 状态
240+
contentUIStateStorage.updateGlobalState({
241+
running: false,
242+
ignored: state.ignored,
243+
interactionMode: state.interactionMode,
244+
demoMode: state.demoMode,
245+
inspecting: state.inspecting,
246+
});
247+
172248
syncStateToContent();
173249
}
174250

pages/content-ui/README.md

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,23 +30,51 @@ Content Script 接收状态更新
3030
UI 组件更新显示状态
3131
```
3232

33+
### 问题修复记录
34+
35+
#### 修复的问题:浏览器重启后诊断模式状态未恢复
36+
37+
**问题原因**
38+
39+
1. Background script 中的状态是硬编码的,没有从 storage 中恢复
40+
2. Content script 没有监听来自 background 的消息
41+
3. Dashboard 组件缺少 `inspecting` 状态的解构
42+
43+
**修复方案**
44+
45+
1. ✅ 在 background script 中添加 `initializeStateFromStorage()` 函数,从 storage 中恢复状态
46+
2. ✅ 在 content script 中添加 Chrome 扩展消息监听器
47+
3. ✅ 修复 Dashboard 组件中缺失的 `inspecting` 状态
48+
4. ✅ 添加详细的日志记录,便于调试
49+
3350
### 测试验证
3451

35-
1. **持久化测试**
36-
- 开启诊断模式
37-
- 关闭浏览器
38-
- 重新打开浏览器
39-
- 验证诊断模式状态是否保持
40-
41-
2. **多页面同步测试**
42-
- 打开多个标签页
43-
- 在其中一个页面开启诊断模式
44-
- 验证其他页面是否同步显示诊断状态
45-
46-
3. **实时同步测试**
47-
- 在页面 A 开启诊断模式
48-
- 在页面 B 关闭诊断模式
49-
- 验证页面 A 是否立即同步关闭状态
52+
#### 1. 持久化测试
53+
54+
1. 开启诊断模式
55+
2. 关闭浏览器
56+
3. 重新打开浏览器
57+
4. 验证诊断模式状态是否保持
58+
59+
#### 2. 多页面同步测试
60+
61+
1. 打开多个标签页
62+
2. 在其中一个页面开启诊断模式
63+
3. 验证其他页面是否同步显示诊断状态
64+
65+
#### 3. 实时同步测试
66+
67+
1. 在页面 A 开启诊断模式
68+
2. 在页面 B 关闭诊断模式
69+
3. 验证页面 A 是否立即同步关闭状态
70+
71+
#### 4. 调试日志验证
72+
73+
打开浏览器开发者工具,查看控制台日志:
74+
75+
- `background: 从 storage 中恢复状态`
76+
- `content: 收到状态更新`
77+
- `content-ui: 收到状态更新`
5078

5179
### Add New Script
5280

pages/content-ui/src/BaseWidget.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export const BaseWidget: React.FC<{
99
style?: React.CSSProperties;
1010
}> = ({ icon, title, value, onClick, style: additionalStyle }) => {
1111
const [isDarkMode, setIsDarkMode] = useState(window.matchMedia('(prefers-color-scheme: dark)').matches);
12+
const [isHovered, setIsHovered] = useState(false);
1213

1314
useEffect(() => {
1415
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
@@ -29,7 +30,8 @@ export const BaseWidget: React.FC<{
2930
borderRadius: '8px',
3031
padding: '2px 4px',
3132
userSelect: 'none',
32-
transition: 'transform 0.3s ease-in-out, opacity 0.3s ease-in-out',
33+
transition: 'transform 0.15s ease-in-out, opacity 0.3s ease-in-out',
34+
transform: isHovered ? 'translateX(-5px)' : 'translateX(0)',
3335
...(isDarkMode
3436
? {
3537
backgroundColor: 'rgba(0, 0, 0, 0.5)',
@@ -51,7 +53,11 @@ export const BaseWidget: React.FC<{
5153

5254
return (
5355
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
54-
<div onClick={onClick} style={style}>
56+
<div
57+
onClick={onClick}
58+
style={style}
59+
onMouseEnter={() => setIsHovered(true)}
60+
onMouseLeave={() => setIsHovered(false)}>
5561
<div
5662
style={{
5763
width: '24px',

pages/content-ui/src/Dashboard.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export const Dashboard: FC = () => {
1414
hovered,
1515
shouldShowOthers,
1616
running,
17+
inspecting, // 添加缺失的 inspecting 状态
1718
// 操作方法
1819
setHovered,
1920
setHoveringOthers,

pages/content-ui/src/DashboardEntry.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export const DashboardEntry: React.FC<{
66
style?: React.CSSProperties;
77
}> = ({ onClick, style: additionalStyle }) => {
88
const [isDarkMode, setIsDarkMode] = useState(window.matchMedia('(prefers-color-scheme: dark)').matches);
9+
const [isHovered, setIsHovered] = useState(false);
910

1011
const icon = (
1112
<img
@@ -40,7 +41,8 @@ export const DashboardEntry: React.FC<{
4041
borderBottomLeftRadius: '12px',
4142
padding: '2px 4px',
4243
userSelect: 'none',
43-
transition: 'transform 0.3s ease-in-out, opacity 0.3s ease-in-out',
44+
transition: 'transform 0.15s ease-in-out, opacity 0.3s ease-in-out',
45+
transform: isHovered ? 'translateX(-5px)' : 'translateX(0)',
4446
cursor: 'pointer',
4547
...(isDarkMode
4648
? {
@@ -62,7 +64,11 @@ export const DashboardEntry: React.FC<{
6264
}
6365
return (
6466
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
65-
<div onClick={onClick} style={style}>
67+
<div
68+
onClick={onClick}
69+
style={style}
70+
onMouseEnter={() => setIsHovered(true)}
71+
onMouseLeave={() => setIsHovered(false)}>
6672
{icon}
6773
<div style={{ display: 'flex', flexDirection: 'column', marginLeft: '4px' }}>
6874
<div style={{ fontSize: '0.9rem', fontWeight: 600, lineHeight: '1.3' }}>RWKV</div>

pages/content/src/contentStart.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { handleNode } from './handleNode';
22
import { injectCss } from './injectcss';
33
import { state } from './state';
44
import { ignoreHref } from '@extension/shared';
5+
import type { AllMessage } from '@extension/shared';
56

67
export const contentStart = () => {
78
injectCss();
@@ -60,8 +61,48 @@ export const contentStart = () => {
6061
}
6162
};
6263

64+
// 监听来自 background 的消息
65+
const handleMessage = (
66+
message: AllMessage,
67+
sender: chrome.runtime.MessageSender,
68+
sendResponse: (response?: any) => void,
69+
) => {
70+
console.log('content: 收到消息', message.func);
71+
72+
const { func } = message;
73+
switch (func) {
74+
case 'GetStateResponse':
75+
case 'OnStateChanged': {
76+
console.log('content: 收到状态更新', message);
77+
// 触发自定义事件,让现有的监听器处理
78+
document.dispatchEvent(new CustomEvent('state-changed', { detail: message }));
79+
break;
80+
}
81+
case 'QueryRequest':
82+
case 'QueryResponse':
83+
case 'SetState':
84+
case 'GetState': {
85+
// 这些消息在 content script 中不需要处理
86+
break;
87+
}
88+
}
89+
};
90+
91+
// 添加消息监听器
92+
chrome.runtime.onMessage.addListener(handleMessage);
93+
6394
document.addEventListener('state-changed', handleStateChanged as EventListener);
6495

96+
// 初始化时请求状态
97+
setTimeout(() => {
98+
console.log('content: 请求初始状态');
99+
try {
100+
chrome.runtime.sendMessage({ func: 'GetState' });
101+
} catch (error) {
102+
console.error('content: 请求状态失败', error);
103+
}
104+
}, 100);
105+
65106
const handleMouseOver = (event: MouseEvent) => {
66107
const target = event.target as HTMLElement;
67108
const isTarget = target.classList.contains('rwkv_offline_target');

0 commit comments

Comments
 (0)