From c740b45e67dca4dcfece9783e992f4394591a5f8 Mon Sep 17 00:00:00 2001 From: wanhose Date: Tue, 27 Feb 2024 19:42:13 +0100 Subject: [PATCH 1/5] chore(browser-extension): upgrade version --- packages/browser-extension/src/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/browser-extension/src/manifest.json b/packages/browser-extension/src/manifest.json index b89f97b..4b7741f 100644 --- a/packages/browser-extension/src/manifest.json +++ b/packages/browser-extension/src/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Cookie Dialog Monster", - "version": "6.4.8", + "version": "7.0.0", "default_locale": "en", "description": "__MSG_appDesc__", "icons": { From ca57b55514cc111210f5069afc6434603f919d94 Mon Sep 17 00:00:00 2001 From: wanhose Date: Tue, 27 Feb 2024 19:43:22 +0100 Subject: [PATCH 2/5] feat(browser-extension): add SET_BADGE handler to the background script and reset action badge text when disabling icon --- packages/browser-extension/src/scripts/background.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/browser-extension/src/scripts/background.js b/packages/browser-extension/src/scripts/background.js index ea4357d..7a47897 100644 --- a/packages/browser-extension/src/scripts/background.js +++ b/packages/browser-extension/src/scripts/background.js @@ -107,6 +107,7 @@ chrome.runtime.onMessage.addListener((message, sender, callback) => { case 'DISABLE_ICON': if (isPage && tabId) { chrome.action.setIcon({ path: '/assets/icons/disabled.png', tabId }, suppressLastError); + chrome.action.setBadgeText({ tabId, text: '' }); } break; case 'ENABLE_ICON': @@ -158,6 +159,12 @@ chrome.runtime.onMessage.addListener((message, sender, callback) => { report(message, sender.tab); } break; + case 'SET_BADGE': + if (tabId) { + chrome.action.setBadgeBackgroundColor({ color: '#6b7280' }); + chrome.action.setBadgeText({ tabId, text: message.value }); + } + break; case 'SET_HOSTNAME_STATE': if (hostname && message.state.enabled === false) { storage.set({ [hostname]: message.state }); From 345cfc6f8828dd92c6ec3c9d9bc902a4d0e23948 Mon Sep 17 00:00:00 2001 From: wanhose Date: Tue, 27 Feb 2024 19:44:39 +0100 Subject: [PATCH 3/5] feat(browser-extension): improve popup script performance and adapt it to send events to the content script in order to avoid refreshing pages when enabling/disabling the extension --- .../browser-extension/src/scripts/popup.js | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/packages/browser-extension/src/scripts/popup.js b/packages/browser-extension/src/scripts/popup.js index d836de9..902d9b4 100644 --- a/packages/browser-extension/src/scripts/popup.js +++ b/packages/browser-extension/src/scripts/popup.js @@ -4,11 +4,6 @@ */ const chromeUrl = 'https://chrome.google.com/webstore/detail/djcbfpkdhdkaflcigibkbpboflaplabg'; -/** - * @description Shortcut to send messages to background script - */ -const dispatch = chrome.runtime.sendMessage; - /** * @description Edge Add-ons link * @type {string} @@ -48,9 +43,9 @@ const isFirefox = navigator.userAgent.indexOf('Firefox') !== -1; /** * @description Extension state - * @type {{ enabled: boolean }} + * @type {{ enabled: boolean, tabId: number | undefined }} */ -let state = { enabled: true }; +let state = { enabled: true, tabId: undefined }; /** * @async @@ -58,12 +53,14 @@ let state = { enabled: true }; * @returns {Promise} */ async function handleContentLoaded() { - const tab = await dispatch({ type: 'GET_TAB' }); + const tab = await chrome.runtime.sendMessage({ type: 'GET_TAB' }); hostname = tab?.url ? new URL(tab.url).hostname.split('.').slice(-3).join('.').replace('www.', '') : undefined; - state = (await dispatch({ hostname, type: 'GET_HOSTNAME_STATE' })) ?? state; + + const next = await chrome.runtime.sendMessage({ hostname, type: 'GET_HOSTNAME_STATE' }); + state = { ...(next ?? state), tabId: tab?.id }; const hostTextElement = document.getElementById('host'); hostTextElement.innerText = hostname ?? 'unknown'; @@ -112,11 +109,13 @@ async function handleLinkRedirect(event) { * @returns {Promise} */ async function handlePowerToggle(event) { - state = { enabled: !state.enabled }; - dispatch({ hostname, state, type: 'SET_HOSTNAME_STATE' }); - if (state.enabled) event.currentTarget.setAttribute('data-value', 'on'); - else event.currentTarget.setAttribute('data-value', 'off'); - await chrome.tabs.reload({ bypassCache: true }); + const element = event.currentTarget; + const next = { enabled: !state.enabled }; + + chrome.runtime.sendMessage({ hostname, state: next, type: 'SET_HOSTNAME_STATE' }); + chrome.tabs.sendMessage(state.tabId, { type: next.enabled ? 'RUN' : 'RESTORE' }); + element.setAttribute('disabled', 'true'); + element.setAttribute('data-value', next.enabled ? 'on' : 'off'); window.close(); } From 96c07952954f332575f3c91ffd6a22f77c85159e Mon Sep 17 00:00:00 2001 From: wanhose Date: Tue, 27 Feb 2024 19:45:15 +0100 Subject: [PATCH 4/5] feat(browser-extension): improve content script performance, improve the visibility logic and adapt it to avoid refreshing pages when enabling/disabling the extension --- .../browser-extension/src/scripts/content.js | 178 +++++++++++++----- 1 file changed, 136 insertions(+), 42 deletions(-) diff --git a/packages/browser-extension/src/scripts/content.js b/packages/browser-extension/src/scripts/content.js index 6f6fbf4..4ba6771 100644 --- a/packages/browser-extension/src/scripts/content.js +++ b/packages/browser-extension/src/scripts/content.js @@ -1,3 +1,14 @@ +/** + * @description Attribute name + */ +const dataAttributeName = 'data-cookie-dialog-monster'; + +/** + * @description Matched elements count + * @type {number} + */ +let count = 0; + /** * @description Data properties * @type {{ classes: string[], commonWords?: string[], fixes: string[], elements: string[], skips: string[], tags: string[] }?} @@ -9,12 +20,23 @@ let data = null; */ const dispatch = chrome.runtime.sendMessage; +/** + * @description Event name + */ +const setupEventName = 'cookie-dialog-monster'; + /** * @description Current hostname * @type {string} */ const hostname = getHostname(); +/** + * @description Elements that were already matched and are removable + * @type {HTMLElement[]} + */ +const removables = []; + /** * @description Options provided to observer * @type {MutationObserverInit} @@ -47,12 +69,22 @@ let state = { enabled: true }; function clean(elements, skipMatch) { for (const element of elements) { if (match(element, skipMatch)) { - const observer = new MutationObserver(() => forceElementStyles(element)); + const observer = new MutationObserver(forceElementStyles); + const options = { attributes: true, attributeFilter: [dataAttributeName, 'class', 'style'] }; - element.setAttribute('data-cookie-dialog-monster', 'true'); + element.setAttribute(dataAttributeName, 'true'); element.style.setProperty('display', 'none', 'important'); - observer.observe(element, { attributes: true, attributeFilter: ['class', 'style'] }); + observer.observe(element, options); + + count += 1; + dispatch({ type: 'SET_BADGE', value: `${count}` }); + + if (!removables.includes(element)) { + removables.push(element); + } } + + seen.push(element); } } @@ -70,11 +102,23 @@ function forceClean(element) { /** * @description Force element to have these styles - * @param {HTMLElement} element - * @returns {void} + * @type {MutationCallback} */ -function forceElementStyles(element) { - element.style.setProperty('display', 'none', 'important'); +function forceElementStyles(mutations, observer) { + for (const mutation of mutations) { + if (mutation.type === 'attributes' && dataAttributeName === mutation.attributeName) { + const element = mutation.target; + const value = element.getAttribute(dataAttributeName); + + if (value === null) { + observer.disconnect(); + element.removeAttribute(dataAttributeName); + element.style.removeProperty('display'); + } else { + element.style.setProperty('display', 'none', 'important'); + } + } + } } /** @@ -119,7 +163,11 @@ function match(element, skipMatch) { return false; } - if (element.getAttribute('data-cookie-dialog-monster')) { + if (element.getAttribute(dataAttributeName)) { + return false; + } + + if (seen.includes(element)) { return false; } @@ -129,12 +177,6 @@ function match(element, skipMatch) { return false; } - if (seen.includes(element)) { - return false; - } - - seen.push(element); - if (element.hasAttributes()) { // 2023-06-10: twitch.tv temporary fix if (element.classList.contains('chat-line__message')) { @@ -168,7 +210,7 @@ function fix() { const skips = (data?.skips ?? []).map((x) => (x.split('.').length < 3 ? `*${x}` : x)); if (backdrop?.children.length === 0) { - backdrop.remove(); + backdrop.style.setProperty('display', 'none'); } for (const fix of fixes) { @@ -176,22 +218,26 @@ function fix() { if (hostname.includes(match)) { switch (action) { - case 'click': - document.querySelector(selector)?.click(); + case 'click': { + const element = document.querySelector(selector); + element?.click(); break; - case 'remove': - document.querySelector(selector)?.style?.removeProperty(property); + } + case 'remove': { + const element = document.querySelector(selector); + element?.style?.removeProperty(property); break; - case 'reset': - document.querySelector(selector)?.style?.setProperty(property, 'initial', 'important'); + } + case 'reset': { + const element = document.querySelector(selector); + element?.style?.setProperty(property, 'initial', 'important'); break; - case 'resetAll': - document.querySelectorAll(selector).forEach((element) => { - element?.style?.setProperty(property, 'initial', 'important'); - }); - break; - default: + } + case 'resetAll': { + const elements = document.querySelectorAll(selector); + elements.forEach((e) => e?.style?.setProperty(property, 'initial', 'important')); break; + } } } } @@ -218,12 +264,38 @@ function readingTime() { return time; } +/** + * @description Restore DOM to its previous state + * @returns {void} + */ +function restoreDOM() { + const backdrop = document.getElementsByClassName('modal-backdrop')[0]; + + if (backdrop?.children.length === 0) { + backdrop.style.removeProperty('display'); + } + + const elements = [...document.querySelectorAll(`[${dataAttributeName}]`)]; + + for (const element of elements) { + element.removeAttribute(dataAttributeName); + } + + for (const element of [document.body, document.documentElement]) { + element?.style.removeProperty('position'); + element?.style.removeProperty('overflow-y'); + } + + count = 0; + seen.splice(0, seen.length); +} + /** * @async * @description Set up everything * @param {boolean} skipReadyStateHack */ -async function runSetup(skipReadyStateHack) { +async function setup(skipReadyStateHack) { state = (await dispatch({ hostname, type: 'GET_HOSTNAME_STATE' })) ?? state; dispatch({ type: 'ENABLE_POPUP' }); @@ -232,11 +304,14 @@ async function runSetup(skipReadyStateHack) { // 2023-06-13: hack to force clean when data request takes too long and there are no changes later if (document.readyState === 'complete' && !skipReadyStateHack) { - window.dispatchEvent(new Event('run')); + window.dispatchEvent(new Event(setupEventName)); } dispatch({ type: 'ENABLE_ICON' }); observer.observe(document.body ?? document.documentElement, options); + } else { + dispatch({ type: 'DISABLE_ICON' }); + observer.disconnect(); } } @@ -251,15 +326,34 @@ const observer = new MutationObserver((mutations) => { if (data?.elements.length && !preview) clean(elements); }); +/** + * @description Listen to messages from any other scripts + * @listens chrome.tabs#onMessage + */ +chrome.runtime.onMessage.addListener((message) => { + switch (message.type) { + case 'RESTORE': { + restoreDOM(); + break; + } + case 'RUN': { + if (removables.length) clean(removables, true); + break; + } + } + + setup(); +}); + /** * @async - * @description Run setup if the page wasn't focused yet - * @listens window#focus + * @description Run setup if the page wasn't visible yet + * @listens window#visibilitychange * @returns {void} */ -window.addEventListener('focus', async () => { - if (document.body && !data) { - await runSetup(true); +window.addEventListener('visibilitychange', async () => { + if (document.body?.children.length && !data) { + await setup(true); clean([...document.body.children]); } }); @@ -270,8 +364,8 @@ window.addEventListener('focus', async () => { * @returns {void} */ window.addEventListener('load', () => { - if (document.hasFocus()) { - window.dispatchEvent(new Event('run')); + if (document.visibilityState === 'visible') { + window.dispatchEvent(new Event(setupEventName)); } }); @@ -281,8 +375,8 @@ window.addEventListener('load', () => { * @returns {void} */ window.addEventListener('pageshow', (event) => { - if (document.hasFocus() && event.persisted) { - window.dispatchEvent(new Event('run')); + if (document.visibilityState === 'visible' && event.persisted) { + window.dispatchEvent(new Event(setupEventName)); } }); @@ -291,8 +385,8 @@ window.addEventListener('pageshow', (event) => { * @listens window#run * @returns {void} */ -window.addEventListener('run', () => { - if (data?.elements.length && document.body && state.enabled && !preview) { +window.addEventListener(setupEventName, () => { + if (data?.elements.length && document.body?.children.length && state.enabled && !preview) { if (readingTime() < 4) { forceClean(document.body); } else { @@ -302,6 +396,6 @@ window.addEventListener('run', () => { } }); -if (document.hasFocus()) { - runSetup(); +if (document.visibilityState === 'visible') { + setup(); } From 72cd81d1262037fc5bbe392077ad43396571b0cb Mon Sep 17 00:00:00 2001 From: wanhose Date: Tue, 27 Feb 2024 20:12:09 +0100 Subject: [PATCH 5/5] feat(browser-extension): increase count if hide backdrop --- packages/browser-extension/src/scripts/content.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/browser-extension/src/scripts/content.js b/packages/browser-extension/src/scripts/content.js index 4ba6771..b437061 100644 --- a/packages/browser-extension/src/scripts/content.js +++ b/packages/browser-extension/src/scripts/content.js @@ -209,8 +209,10 @@ function fix() { const fixes = data?.fixes ?? []; const skips = (data?.skips ?? []).map((x) => (x.split('.').length < 3 ? `*${x}` : x)); - if (backdrop?.children.length === 0) { + if (backdrop?.children.length === 0 && backdrop.style.display !== 'none') { backdrop.style.setProperty('display', 'none'); + count += 1; + dispatch({ type: 'SET_BADGE', value: `${count}` }); } for (const fix of fixes) {