fix: lighten debounce() usage, do not do full re-render on every change

This commit is contained in:
2025-11-20 14:09:43 +03:00
parent dfdc1742ec
commit 348d64f356
3 changed files with 43 additions and 49 deletions

View File

@@ -2,7 +2,6 @@ import { HighlightList, MessageData } from '../types.js';
import { StorageService } from '../services/StorageService.js'; import { StorageService } from '../services/StorageService.js';
import { MessageService } from '../services/MessageService.js'; import { MessageService } from '../services/MessageService.js';
import { HighlightEngine } from './HighlightEngine.js'; import { HighlightEngine } from './HighlightEngine.js';
import { DOMUtils } from '../utils/DOMUtils.js';
export class ContentScript { export class ContentScript {
private lists: HighlightList[] = []; private lists: HighlightList[] = [];
@@ -22,7 +21,6 @@ export class ContentScript {
private async initialize(): Promise<void> { private async initialize(): Promise<void> {
await this.loadSettings(); await this.loadSettings();
this.setupMessageListener(); this.setupMessageListener();
this.setupScrollHandler();
this.processHighlights(); this.processHighlights();
} }
@@ -67,10 +65,6 @@ export class ContentScript {
}); });
} }
private setupScrollHandler(): void {
const debouncedProcess = DOMUtils.debounce(() => this.processHighlights(), CONSTANTS.DEBOUNCE_DELAY);
window.addEventListener('scroll', debouncedProcess);
}
private async handleWordListUpdate(): Promise<void> { private async handleWordListUpdate(): Promise<void> {
const data = await StorageService.get(['lists']); const data = await StorageService.get(['lists']);

View File

@@ -193,7 +193,7 @@ export class PopupController {
} }
}); });
wordList.addEventListener('input', (e) => { wordList.addEventListener('change', (e) => {
const target = e.target as HTMLInputElement; const target = e.target as HTMLInputElement;
const index = +(target.dataset.bgEdit ?? target.dataset.fgEdit ?? -1); const index = +(target.dataset.bgEdit ?? target.dataset.fgEdit ?? -1);
if (index === -1) return; if (index === -1) return;
@@ -219,13 +219,14 @@ export class PopupController {
} }
}); });
let scrollTimeout: number; let scrolling = false;
wordList.addEventListener('scroll', () => { wordList.addEventListener('scroll', () => {
if (scrollTimeout) return; if (scrolling) return;
scrollTimeout = window.setTimeout(() => { scrolling = true;
requestAnimationFrame(() => this.renderWords()); requestAnimationFrame(() => {
scrollTimeout = 0; this.renderWords();
}, 16); scrolling = false;
});
}); });
} }
@@ -278,24 +279,38 @@ export class PopupController {
const matchCase = document.getElementById('matchCase') as HTMLInputElement; const matchCase = document.getElementById('matchCase') as HTMLInputElement;
const matchWhole = document.getElementById('matchWhole') as HTMLInputElement; const matchWhole = document.getElementById('matchWhole') as HTMLInputElement;
globalToggle.addEventListener('change', () => { globalToggle.addEventListener('change', async () => {
this.globalHighlightEnabled = globalToggle.checked; this.globalHighlightEnabled = globalToggle.checked;
this.updateGlobalToggleState(); await StorageService.update('globalHighlightEnabled', this.globalHighlightEnabled);
MessageService.sendToAllTabs({
type: 'GLOBAL_TOGGLE_UPDATED',
enabled: this.globalHighlightEnabled
});
}); });
matchCase.addEventListener('change', () => { matchCase.addEventListener('change', async () => {
this.matchCaseEnabled = matchCase.checked; this.matchCaseEnabled = matchCase.checked;
this.save(); await StorageService.update('matchCaseEnabled', this.matchCaseEnabled);
MessageService.sendToAllTabs({
type: 'MATCH_OPTIONS_UPDATED',
matchCase: this.matchCaseEnabled,
matchWhole: this.matchWholeEnabled
});
}); });
matchWhole.addEventListener('change', () => { matchWhole.addEventListener('change', async () => {
this.matchWholeEnabled = matchWhole.checked; this.matchWholeEnabled = matchWhole.checked;
this.save(); await StorageService.update('matchWholeEnabled', this.matchWholeEnabled);
MessageService.sendToAllTabs({
type: 'MATCH_OPTIONS_UPDATED',
matchCase: this.matchCaseEnabled,
matchWhole: this.matchWholeEnabled
});
}); });
} }
private setupExceptions(): void { private setupExceptions(): void {
document.getElementById('toggleExceptionBtn')?.addEventListener('click', () => { document.getElementById('toggleExceptionBtn')?.addEventListener('click', async () => {
if (!this.currentTabHost) return; if (!this.currentTabHost) return;
const isException = this.exceptionsList.includes(this.currentTabHost); const isException = this.exceptionsList.includes(this.currentTabHost);
@@ -308,26 +323,29 @@ export class PopupController {
this.updateExceptionButton(); this.updateExceptionButton();
this.renderExceptions(); this.renderExceptions();
this.save(); await StorageService.update('exceptionsList', this.exceptionsList);
MessageService.sendToAllTabs({ type: 'EXCEPTIONS_LIST_UPDATED' });
}); });
document.getElementById('clearExceptionsBtn')?.addEventListener('click', () => { document.getElementById('clearExceptionsBtn')?.addEventListener('click', async () => {
if (confirm(chrome.i18n.getMessage('confirm_clear_exceptions') || 'Clear all exceptions?')) { if (confirm(chrome.i18n.getMessage('confirm_clear_exceptions') || 'Clear all exceptions?')) {
this.exceptionsList = []; this.exceptionsList = [];
this.updateExceptionButton(); this.updateExceptionButton();
this.renderExceptions(); this.renderExceptions();
this.save(); await StorageService.update('exceptionsList', this.exceptionsList);
MessageService.sendToAllTabs({ type: 'EXCEPTIONS_LIST_UPDATED' });
} }
}); });
document.getElementById('exceptionsList')?.addEventListener('click', (e) => { document.getElementById('exceptionsList')?.addEventListener('click', async (e) => {
const target = e.target as HTMLElement; const target = e.target as HTMLElement;
if (target.classList.contains('exception-remove')) { if (target.classList.contains('exception-remove')) {
const domain = target.dataset.domain!; const domain = target.dataset.domain!;
this.exceptionsList = this.exceptionsList.filter(d => d !== domain); this.exceptionsList = this.exceptionsList.filter(d => d !== domain);
this.updateExceptionButton(); this.updateExceptionButton();
this.renderExceptions(); this.renderExceptions();
this.save(); await StorageService.update('exceptionsList', this.exceptionsList);
MessageService.sendToAllTabs({ type: 'EXCEPTIONS_LIST_UPDATED' });
} }
}); });
} }
@@ -436,29 +454,13 @@ export class PopupController {
exceptionsList: this.exceptionsList exceptionsList: this.exceptionsList
}); });
this.render(); this.renderLists();
MessageService.sendToAllTabs({ type: 'WORD_LIST_UPDATED' }); MessageService.sendToAllTabs({ type: 'WORD_LIST_UPDATED' });
MessageService.sendToAllTabs({
type: 'GLOBAL_TOGGLE_UPDATED',
enabled: this.globalHighlightEnabled
});
MessageService.sendToAllTabs({
type: 'MATCH_OPTIONS_UPDATED',
matchCase: this.matchCaseEnabled,
matchWhole: this.matchWholeEnabled
});
MessageService.sendToAllTabs({ type: 'EXCEPTIONS_LIST_UPDATED' });
} }
private async updateGlobalToggleState(): Promise<void> {
await StorageService.update('globalHighlightEnabled', this.globalHighlightEnabled);
MessageService.sendToAllTabs({
type: 'GLOBAL_TOGGLE_UPDATED',
enabled: this.globalHighlightEnabled
});
}
private render(): void { private render(): void {
this.renderLists(); this.renderLists();
@@ -500,11 +502,9 @@ export class PopupController {
const totalItemHeight = itemHeight + itemSpacing; const totalItemHeight = itemHeight + itemSpacing;
const containerHeight = wordList.clientHeight || 250; const containerHeight = wordList.clientHeight || 250;
const scrollTop = wordList.scrollTop; const scrollTop = wordList.scrollTop;
const startIndex = Math.floor(scrollTop / totalItemHeight); const startIndex = Math.max(0, Math.floor(scrollTop / totalItemHeight) - 1);
const endIndex = Math.min( const visibleCount = Math.ceil(containerHeight / totalItemHeight);
startIndex + Math.ceil(containerHeight / totalItemHeight) + 2, const endIndex = Math.min(startIndex + visibleCount + 2, filteredWords.length);
filteredWords.length
);
wordList.innerHTML = ''; wordList.innerHTML = '';

View File

@@ -50,6 +50,6 @@ export const DEFAULT_STORAGE: StorageData = {
export const CONSTANTS = { export const CONSTANTS = {
WORD_ITEM_HEIGHT: 32, WORD_ITEM_HEIGHT: 32,
DEBOUNCE_DELAY: 300, DEBOUNCE_DELAY: 150,
SCROLL_THROTTLE: 16 SCROLL_THROTTLE: 16
} as const; } as const;