From 7826046f7664c56ed5d85f2111a851c6397f369f Mon Sep 17 00:00:00 2001 From: Joel Brock Date: Thu, 23 Apr 2026 08:58:45 -0700 Subject: [PATCH] feat: MutationObserver for dynamic element suppression --- content/content.js | 18 +++++++ content/observer.js | 127 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 144 insertions(+), 1 deletion(-) diff --git a/content/content.js b/content/content.js index 6fb66ef..66552cb 100644 --- a/content/content.js +++ b/content/content.js @@ -89,6 +89,15 @@ applySettingsToDOM(settings); injectThemeCSS(settings.theme); + // Start the MutationObserver + OR.Observer.start(settings); + + // Apply behavior patches + OR.Behavior.start(settings); + + // Start DOM injector (quick actions) + OR.Injector.start(settings); + // Listen for setting changes from popup chrome.storage.onChanged.addListener((changes, area) => { if (area !== 'sync') return; @@ -97,6 +106,15 @@ OR.loadSettings().then((updated) => { applySettingsToDOM(updated); + // Update observer with new settings + OR.Observer.updateSettings(updated); + + // Update behavior patches + OR.Behavior.updateSettings(updated); + + // Update injector + OR.Injector.updateSettings(updated); + // Swap theme CSS if theme changed if (changes.theme) { injectThemeCSS(changes.theme.newValue); diff --git a/content/observer.js b/content/observer.js index 6cbd191..608c78d 100644 --- a/content/observer.js +++ b/content/observer.js @@ -1 +1,126 @@ -// Outlook Relook — observer.js (stub, implemented in later task) +// Outlook Relook — MutationObserver +// Watches OWA's DOM and removes dynamically injected elements +// based on active settings. + +window.OutlookRelook = window.OutlookRelook || {}; + +window.OutlookRelook.Observer = (function () { + 'use strict'; + + let observer = null; + let currentSettings = {}; + + // Map setting keys to selector registry names + // Each setting can suppress one or more logical elements + const SETTING_TO_SELECTORS = { + hideCopilot: ['copilot-button', 'copilot-pane', 'copilot-compose-suggestions'], + hideSuggestedReplies: ['suggested-replies'], + hidePromoBanners: ['promo-banners'], + hideFocusedOtherTabs: ['focused-other-tabs'], + hideSidebarAppIcons: ['sidebar-app-icons'], + hideGroupsSection: ['groups-section'], + hideMyDayButtons: ['my-day-buttons'], + hideSenderAvatars: ['sender-avatars'], + hideFeatureDiscovery: ['feature-discovery'], + hideVivaInsights: ['viva-insights'], + hideUnreadOtherBanner: ['unread-other-banner'], + hideActivityFeed: ['activity-feed'], + }; + + // Text content patterns to match elements by their inner text. + // Used when aria/data selectors don't catch dynamically injected content. + const TEXT_PATTERNS = { + hidePromoBanners: [ + /try the new outlook/i, + /upgrade to premium/i, + /get the outlook app/i, + /switch to the new/i, + ], + hideFeatureDiscovery: [ + /what's new/i, + /new feature/i, + /did you know/i, + ], + }; + + function suppressElements() { + for (const [settingKey, selectorNames] of Object.entries(SETTING_TO_SELECTORS)) { + if (!currentSettings[settingKey]) continue; + + for (const name of selectorNames) { + const elements = window.OutlookRelook.resolveSelector(name); + for (const el of elements) { + if (el.style.display !== 'none') { + el.style.display = 'none'; + console.log('[Outlook Relook] Suppressed: ' + name, el); + } + } + } + } + + // Text-based suppression for elements missed by selectors + for (const [settingKey, patterns] of Object.entries(TEXT_PATTERNS)) { + if (!currentSettings[settingKey]) continue; + + for (const pattern of patterns) { + // Check buttons, banners, and generic containers + const candidates = document.querySelectorAll( + '[role="alert"], [role="banner"], [role="dialog"], [role="status"], button, a' + ); + for (const el of candidates) { + if (el.style.display === 'none') continue; + var text = el.textContent || ''; + if (pattern.test(text) && text.length < 200) { + el.style.display = 'none'; + console.log('[Outlook Relook] Suppressed by text: "' + text.trim().substring(0, 50) + '"', el); + } + } + } + } + } + + function start(settings) { + currentSettings = settings; + + // Initial pass + suppressElements(); + + // Watch for DOM mutations + observer = new MutationObserver(function (mutations) { + // Debounce: only process if nodes were actually added + var hasAddedNodes = false; + for (var i = 0; i < mutations.length; i++) { + if (mutations[i].addedNodes.length > 0) { + hasAddedNodes = true; + break; + } + } + if (hasAddedNodes) { + suppressElements(); + } + }); + + observer.observe(document.body, { + childList: true, + subtree: true, + }); + + console.log('[Outlook Relook] Observer started'); + } + + function updateSettings(settings) { + currentSettings = settings; + // Re-run suppression with new settings + suppressElements(); + } + + function stop() { + if (observer) { + observer.disconnect(); + observer = null; + console.log('[Outlook Relook] Observer stopped'); + } + } + + return { start: start, updateSettings: updateSettings, stop: stop }; +})();