feat: MutationObserver for dynamic element suppression

This commit is contained in:
Joel Brock
2026-04-23 08:58:45 -07:00
parent 95eb177754
commit 7826046f76
2 changed files with 144 additions and 1 deletions

View File

@@ -89,6 +89,15 @@
applySettingsToDOM(settings); applySettingsToDOM(settings);
injectThemeCSS(settings.theme); 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 // Listen for setting changes from popup
chrome.storage.onChanged.addListener((changes, area) => { chrome.storage.onChanged.addListener((changes, area) => {
if (area !== 'sync') return; if (area !== 'sync') return;
@@ -97,6 +106,15 @@
OR.loadSettings().then((updated) => { OR.loadSettings().then((updated) => {
applySettingsToDOM(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 // Swap theme CSS if theme changed
if (changes.theme) { if (changes.theme) {
injectThemeCSS(changes.theme.newValue); injectThemeCSS(changes.theme.newValue);

View File

@@ -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 };
})();