Skip to content

Commit 2fa4ce0

Browse files
committed
Invalidate cleared chat handoff snapshots
1 parent 4ffb972 commit 2fa4ce0

3 files changed

Lines changed: 22 additions & 3 deletions

File tree

src/chrome/src/ui/sidepanel.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1644,6 +1644,10 @@ function clearCachedTabChat(tabId) {
16441644
persistTimer = null;
16451645
persistTimerTabId = null;
16461646
}
1647+
if (lastVisibleTabChatSnapshot
1648+
&& sameTabId(lastVisibleTabChatSnapshot.tabId, tabId)) {
1649+
lastVisibleTabChatSnapshot = null;
1650+
}
16471651
tabChats.delete(tabId);
16481652
return enqueueTabChatOperation(tabId, async (numericTabId) => {
16491653
tabChats.delete(numericTabId);
@@ -2095,7 +2099,7 @@ function drainQueuedComposerMessageForCurrentTab() {
20952099
}
20962100

20972101
async function renderClearedConversationForTab(tabId) {
2098-
clearCachedTabChat(tabId);
2102+
await clearCachedTabChat(tabId);
20992103
resetComposerHistoryNavigation(tabId);
21002104
saveInputDraftForTab(tabId, '');
21012105
clearPendingAttachmentsForTab(tabId);
@@ -2112,6 +2116,10 @@ async function renderClearedConversationForTab(tabId) {
21122116
autoResizeInput();
21132117
syncSendButtonState();
21142118
addMessage('system', t('sp.cleared_message'));
2119+
const clearedHtml = messagesEl.innerHTML;
2120+
lastVisibleTabChatSnapshot = { tabId: Number(tabId), html: clearedHtml };
2121+
tabChats.set(Number(tabId), clearedHtml);
2122+
await persistTabChat(tabId, clearedHtml, { allowHidden: true }).catch(() => {});
21152123
refreshScheduledJobs({ tabId });
21162124
refreshRecommendedActions();
21172125
}

src/firefox/src/ui/sidepanel.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1509,6 +1509,10 @@ function clearCachedTabChat(tabId) {
15091509
persistTimer = null;
15101510
persistTimerTabId = null;
15111511
}
1512+
if (lastVisibleTabChatSnapshot
1513+
&& sameTabId(lastVisibleTabChatSnapshot.tabId, tabId)) {
1514+
lastVisibleTabChatSnapshot = null;
1515+
}
15121516
tabChats.delete(tabId);
15131517
return enqueueTabChatOperation(tabId, async (numericTabId) => {
15141518
tabChats.delete(numericTabId);
@@ -2216,7 +2220,7 @@ function drainQueuedComposerMessageForCurrentTab() {
22162220
}
22172221

22182222
async function renderClearedConversationForTab(tabId) {
2219-
clearCachedTabChat(tabId);
2223+
await clearCachedTabChat(tabId);
22202224
resetComposerHistoryNavigation(tabId);
22212225
saveInputDraftForTab(tabId, '');
22222226
clearPendingAttachmentsForTab(tabId);
@@ -2233,6 +2237,10 @@ async function renderClearedConversationForTab(tabId) {
22332237
autoResizeInput();
22342238
syncSendButtonState();
22352239
addMessage('system', t('sp.cleared_message'));
2240+
const clearedHtml = messagesEl.innerHTML;
2241+
lastVisibleTabChatSnapshot = { tabId: Number(tabId), html: clearedHtml };
2242+
tabChats.set(Number(tabId), clearedHtml);
2243+
await persistTabChat(tabId, clearedHtml, { allowHidden: true }).catch(() => {});
22362244
refreshScheduledJobs({ tabId });
22372245
refreshRecommendedActions();
22382246
}

test/run.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17865,7 +17865,8 @@ test('sidepanel deletes durable history when clearing conversations', () => {
1786517865
const helperStart = panel.indexOf('async function renderClearedConversationForTab(tabId)');
1786617866
assert.notEqual(helperStart, -1, `${label}: clear helper should be async`);
1786717867
const helperBody = panel.slice(helperStart, panel.indexOf('refreshScheduledJobs({', helperStart));
17868-
assert.match(helperBody, /await resetChatHistoryStateForTab\(tabId\);[\s\S]*?if \(currentTabId !== tabId\) return;/, `${label}: clear helper should delete durable history before visible-tab guards`);
17868+
assert.match(helperBody, /await clearCachedTabChat\(tabId\);[\s\S]*?await resetChatHistoryStateForTab\(tabId\);[\s\S]*?if \(currentTabId !== tabId\) return;/, `${label}: clear helper should durably clear tab chat before deleting history and checking visibility`);
17869+
assert.match(helperBody, /addMessage\('system', t\('sp\.cleared_message'\)\);[\s\S]*?lastVisibleTabChatSnapshot = \{ tabId: Number\(tabId\), html: clearedHtml \};[\s\S]*?await persistTabChat\(tabId, clearedHtml, \{ allowHidden: true \}\)/, `${label}: a cleared handoff snapshot should replace the invalidated transcript only after the durable clear`);
1786917870

1787017871
const resetIdx = panel.indexOf("if (command.value === '/reset')");
1787117872
const resetSlashBody = panel.slice(resetIdx, panel.indexOf("if (command.value === '/screenshot'", resetIdx));
@@ -18221,6 +18222,7 @@ test('chrome sidepanel serializes tab-chat storage writes with clears and reads'
1822118222
const clearBody = panel.slice(clearStart, panel.indexOf('\n}\n\nasync function renderClearedConversationForTab', clearStart) + 2);
1822218223
assert.match(clearBody, /tabChats\.delete\(tabId\);/, 'chrome: clearing tab chat should delete the cached HTML before queuing storage removal');
1822318224
assert.match(clearBody, /return enqueueTabChatOperation\(tabId, async \(numericTabId\) => \{/, 'chrome: clearing tab chat should be serialized through the queue');
18225+
assert.match(clearBody, /lastVisibleTabChatSnapshot[\s\S]*?sameTabId\(lastVisibleTabChatSnapshot\.tabId, tabId\)[\s\S]*?lastVisibleTabChatSnapshot = null;[\s\S]*?return enqueueTabChatOperation/, 'chrome: clearing should invalidate the pre-clear handoff snapshot before yielding');
1822418226
assert.match(clearBody, /return enqueueTabChatOperation\(tabId, async \(numericTabId\) => \{[\s\S]*?tabChats\.delete\(numericTabId\);[\s\S]*?handoffGeneration = tabChatHandoffGenerations\.get\(numericTabId\);[\s\S]*?sendToBackground\('load_tab_chat', \{[\s\S]*?waitForHandoff: true,[\s\S]*?sendToBackground\('clear_tab_chat', \{[\s\S]*?handoffOwnerId: tabChatHandoffOwnerId,[\s\S]*?handoffGeneration,/, 'chrome: queued clears should retain or reacquire ownership before clearing shared state');
1822518227
assert.doesNotMatch(clearBody, /tabChatHandoffGenerations\.delete/, 'chrome: clearing content must not discard the visible document handoff generation');
1822618228
});
@@ -18244,6 +18246,7 @@ test('firefox sidepanel serializes tab-chat storage writes with clears and reads
1824418246
const clearBody = panel.slice(clearStart, panel.indexOf('\n}\n\n// Save current tab', clearStart) + 2);
1824518247
assert.match(clearBody, /tabChats\.delete\(tabId\);/, 'firefox: clearing tab chat should delete the cached HTML before queuing storage removal');
1824618248
assert.match(clearBody, /return enqueueTabChatOperation\(tabId, async \(numericTabId\) => \{/, 'firefox: clearing tab chat should be serialized through the queue');
18249+
assert.match(clearBody, /lastVisibleTabChatSnapshot[\s\S]*?sameTabId\(lastVisibleTabChatSnapshot\.tabId, tabId\)[\s\S]*?lastVisibleTabChatSnapshot = null;[\s\S]*?return enqueueTabChatOperation/, 'firefox: clearing should invalidate the pre-clear handoff snapshot before yielding');
1824718250
assert.match(clearBody, /return enqueueTabChatOperation\(tabId, async \(numericTabId\) => \{[\s\S]*?tabChats\.delete\(numericTabId\);[\s\S]*?handoffGeneration = tabChatHandoffGenerations\.get\(numericTabId\);[\s\S]*?sendToBackground\('load_tab_chat', \{[\s\S]*?waitForHandoff: true,[\s\S]*?sendToBackground\('clear_tab_chat', \{[\s\S]*?handoffOwnerId: tabChatHandoffOwnerId,[\s\S]*?handoffGeneration,/, 'firefox: queued clears should retain or reacquire ownership before clearing shared state');
1824818251
assert.doesNotMatch(clearBody, /tabChatHandoffGenerations\.delete/, 'firefox: clearing content must not discard the visible document handoff generation');
1824918252
});

0 commit comments

Comments
 (0)