|
44 | 44 |
|
45 | 45 | let ruleSyncIntervalId = null; |
46 | 46 | let ruleSyncInFlight = false; |
| 47 | + let isFinalizingSession = false; |
| 48 | + const pendingSaveOperations = new Set(); |
| 49 | + |
| 50 | + function trackPendingSave(operationPromise) { |
| 51 | + let trackedPromise; |
| 52 | + trackedPromise = Promise.resolve(operationPromise) |
| 53 | + .catch(() => {}) |
| 54 | + .finally(() => { |
| 55 | + pendingSaveOperations.delete(trackedPromise); |
| 56 | + }); |
| 57 | + pendingSaveOperations.add(trackedPromise); |
| 58 | + return trackedPromise; |
| 59 | + } |
| 60 | + |
| 61 | + async function waitForPendingSaves(timeoutMs = 1500) { |
| 62 | + if (pendingSaveOperations.size === 0) return; |
| 63 | + const pending = Array.from(pendingSaveOperations); |
| 64 | + await Promise.race([ |
| 65 | + Promise.allSettled(pending), |
| 66 | + new Promise((resolve) => setTimeout(resolve, timeoutMs)) |
| 67 | + ]); |
| 68 | + } |
| 69 | + |
| 70 | + async function finalizeSessionAndReload() { |
| 71 | + if (isFinalizingSession) return; |
| 72 | + isFinalizingSession = true; |
| 73 | + |
| 74 | + try { |
| 75 | + await waitForPendingSaves(); |
| 76 | + deactivateZapper({ removeUi: true }); |
| 77 | + window.location.reload(); |
| 78 | + } catch { |
| 79 | + deactivateZapper({ removeUi: true }); |
| 80 | + } finally { |
| 81 | + isFinalizingSession = false; |
| 82 | + } |
| 83 | + } |
47 | 84 |
|
48 | 85 | function safeHostname() { |
49 | 86 | try { |
|
423 | 460 | e.stopPropagation(); |
424 | 461 | if (typeof e.stopImmediatePropagation === 'function') e.stopImmediatePropagation(); |
425 | 462 | } |
426 | | - // Delay teardown until after the current input/click sequence completes |
427 | | - // so we don't accidentally retarget the synthetic click to the page. |
428 | | - setTimeout(() => deactivateZapper({ removeUi: true }), 0); |
| 463 | + doneButton.disabled = true; |
| 464 | + finalizeSessionAndReload().catch(() => {}); |
429 | 465 | }; |
430 | 466 | doneButton.addEventListener('click', onDone); |
431 | 467 | doneButton.addEventListener('pointerup', onDone, true); |
|
690 | 726 | } |
691 | 727 | state.rules = state.rules.concat([normalized]).slice(0, MAX_RULES_PER_SITE); |
692 | 728 | state.undoStack.push(normalized); |
693 | | - await saveRulesForHost(state.host, state.rules); |
| 729 | + await trackPendingSave(saveRulesForHost(state.host, state.rules)); |
694 | 730 | applyRulesToPage(state.rules); |
695 | 731 | if (state.ui.undoButton) state.ui.undoButton.disabled = false; |
696 | 732 | showToast(options.manual ? 'Rule saved for this site.' : 'Hidden. Rule saved for this site.'); |
|
713 | 749 | if (state.undoStack.length === 0) return; |
714 | 750 | const toRemove = state.undoStack.pop(); |
715 | 751 | state.rules = state.rules.filter((r) => r !== toRemove); |
716 | | - await saveRulesForHost(state.host, state.rules); |
| 752 | + await trackPendingSave(saveRulesForHost(state.host, state.rules)); |
717 | 753 | applyRulesToPage(state.rules); |
718 | 754 | if (state.ui.undoButton) state.ui.undoButton.disabled = state.undoStack.length === 0; |
719 | 755 | showToast('Undone.'); |
|
0 commit comments