diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 3f836a2..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "kiroAgent.configureMCP": "Enabled" -} \ No newline at end of file diff --git a/src/content/ContentScript.ts b/src/content/ContentScript.ts index 2beda51..d45185d 100644 --- a/src/content/ContentScript.ts +++ b/src/content/ContentScript.ts @@ -12,6 +12,7 @@ export class ContentScript { private matchCase = false; private matchWhole = false; private highlightEngine: HighlightEngine; + private isProcessing = false; constructor() { this.highlightEngine = new HighlightEngine(() => this.processHighlights()); @@ -96,11 +97,19 @@ export class ContentScript { } private processHighlights(): void { - if (!this.isGlobalHighlightEnabled || this.isCurrentSiteException) { - this.highlightEngine.clearHighlights(); - return; - } + if (this.isProcessing) return; + this.isProcessing = true; - this.highlightEngine.highlight(this.lists, this.matchCase, this.matchWhole); + try { + if (!this.isGlobalHighlightEnabled || this.isCurrentSiteException) { + this.highlightEngine.clearHighlights(); + this.highlightEngine.stopObserving(); + return; + } + + this.highlightEngine.highlight(this.lists, this.matchCase, this.matchWhole); + } finally { + this.isProcessing = false; + } } } \ No newline at end of file diff --git a/src/content/HighlightEngine.ts b/src/content/HighlightEngine.ts index c482e8b..bf27989 100644 --- a/src/content/HighlightEngine.ts +++ b/src/content/HighlightEngine.ts @@ -7,7 +7,23 @@ export class HighlightEngine { private observer: MutationObserver; constructor(private onUpdate: () => void) { - this.observer = new MutationObserver(DOMUtils.debounce(onUpdate, 300)); + this.observer = new MutationObserver(DOMUtils.debounce((mutations: MutationRecord[]) => { + const hasRelevantChanges = mutations.some((mutation: MutationRecord) => { + if (mutation.target instanceof Element && mutation.target.hasAttribute('data-gh')) { + return false; + } + const addedNodes = Array.from(mutation.addedNodes); + const removedNodes = Array.from(mutation.removedNodes); + const isOurChange = [...addedNodes, ...removedNodes].some(node => + node instanceof Element && (node.hasAttribute('data-gh') || node.querySelector('[data-gh]')) + ); + return !isOurChange; + }); + + if (hasRelevantChanges) { + onUpdate(); + } + }, 300)); } private initializeStyleSheet(): void { @@ -45,14 +61,8 @@ export class HighlightEngine { } clearHighlights(): void { - const highlightedElements = document.querySelectorAll('[data-gh]'); - highlightedElements.forEach(element => { - const parent = element.parentNode; - if (parent) { - parent.replaceChild(document.createTextNode(element.textContent || ''), element); - parent.normalize(); - } - }); + this.observer.disconnect(); + this.clearHighlightsInternal(); } private getTextNodes(): Text[] { @@ -100,7 +110,8 @@ export class HighlightEngine { highlight(lists: HighlightList[], matchCase: boolean, matchWhole: boolean): void { this.observer.disconnect(); - this.clearHighlights(); + + this.clearHighlightsInternal(); const activeWords = this.extractActiveWords(lists); if (activeWords.length === 0) { @@ -146,11 +157,34 @@ export class HighlightEngine { this.startObserving(); } + private clearHighlightsInternal(): void { + const highlightedElements = document.querySelectorAll('[data-gh]'); + highlightedElements.forEach(element => { + const parent = element.parentNode; + if (parent) { + parent.replaceChild(document.createTextNode(element.textContent || ''), element); + parent.normalize(); + } + }); + + if (this.styleSheet && this.styleSheet.cssRules.length > 0) { + while (this.styleSheet.cssRules.length > 0) { + this.styleSheet.deleteRule(0); + } + } + } + + stopObserving(): void { + this.observer.disconnect(); + } + private startObserving(): void { this.observer.observe(document.body, { childList: true, subtree: true, - characterData: true + characterData: true, + // Don't observe attribute changes to avoid triggering on our own style changes + attributes: false }); }