From 33a2e096a9403856ec878b528902f6a7e7df9e0a Mon Sep 17 00:00:00 2001 From: wanhose Date: Fri, 18 Oct 2024 16:30:45 +0200 Subject: [PATCH 1/3] chore(browser-extension): bump extension 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 f8f8183..b626973 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": "8.0.1", + "version": "8.0.2", "default_locale": "en", "description": "__MSG_appDesc__", "icons": { From a5766071fd24ec377acfd667a66908272ba2c730 Mon Sep 17 00:00:00 2001 From: wanhose Date: Fri, 18 Oct 2024 16:31:43 +0200 Subject: [PATCH 2/3] chore(browser-extension): drop exclude_matches from content_scripts --- packages/browser-extension/src/manifest.json | 22 -------------------- 1 file changed, 22 deletions(-) diff --git a/packages/browser-extension/src/manifest.json b/packages/browser-extension/src/manifest.json index b626973..cdde134 100644 --- a/packages/browser-extension/src/manifest.json +++ b/packages/browser-extension/src/manifest.json @@ -29,28 +29,6 @@ "content_scripts": [ { "all_frames": true, - "exclude_matches": [ - "*://*.bauhaus.cz/*", - "*://*.codesandbox.io/*", - "*://*.facebook.com/*", - "*://*.googleapis.com/embed/*", - "*://*.olympics.com/*", - "*://*.youtube-nocookie.com/embed/*", - "*://*.youtube.com/embed/*", - "*://www.youtube.com/*", - "*://translate.google.ca/*", - "*://translate.google.co.in/*", - "*://translate.google.co.jp/*", - "*://translate.google.co.uk/*", - "*://translate.google.com.au/*", - "*://translate.google.com.br/*", - "*://translate.google.com/*", - "*://translate.google.de/*", - "*://translate.google.es/*", - "*://translate.google.fr/*", - "*://translate.google.it/*", - "*://www.cookie-dialog-monster.com/*" - ], "js": ["scripts/content.js"], "matches": ["http://*/*", "https://*/*"], "run_at": "document_start" From 7d8d3957ee80287dbdc6a722af67310b7415706d Mon Sep 17 00:00:00 2001 From: wanhose Date: Fri, 18 Oct 2024 16:44:09 +0200 Subject: [PATCH 3/3] feat(browser-extension): adapt code to database exclusions and fix minor bugs --- .../src/scripts/background.js | 57 +--- .../browser-extension/src/scripts/content.js | 260 +++++++++--------- 2 files changed, 144 insertions(+), 173 deletions(-) diff --git a/packages/browser-extension/src/scripts/background.js b/packages/browser-extension/src/scripts/background.js index c0bbc20..f51a736 100644 --- a/packages/browser-extension/src/scripts/background.js +++ b/packages/browser-extension/src/scripts/background.js @@ -49,7 +49,7 @@ class RequestManager { * @description API URL * @type {string} */ -const apiUrl = 'https://api.cookie-dialog-monster.com/rest/v5'; +const apiUrl = 'https://api.cookie-dialog-monster.com/rest/v6'; /** * @description Context menu identifier @@ -171,34 +171,6 @@ async function getState(hostname) { return { ...stateByDefault, ...state, updateAvailable }; } -/** - * @description Format number to avoid errors - * @param {number} [value] - * @returns {string | null} - */ -function formatNumber(value) { - if (value) { - if (value >= 1e6) { - return `${Math.floor(value / 1e6)}M`; - } else if (value >= 1e3) { - return `${Math.floor(value / 1e3)}K`; - } else { - return value.toString(); - } - } - - return null; -} - -/** - * @description Convert match string to pattern string - * @param {string} match - * @returns {string} - */ -function matchToPattern(match) { - return `^${match.replaceAll('*.', '*(.)?').replaceAll('*', '.*')}$`; -} - /** * @async * @description Refresh data @@ -370,7 +342,7 @@ browser.runtime.onMessage.addListener((message, sender, callback) => { case 'UPDATE_BADGE': if (isPage && tabId !== undefined) { browser.action.setBadgeBackgroundColor({ color: '#6b7280' }); - browser.action.setBadgeText({ tabId, text: formatNumber(message.value) }); + browser.action.setBadgeText({ tabId, text: message.value ? `${message.value}` : null }); } break; case 'UPDATE_STORE': @@ -419,6 +391,7 @@ browser.runtime.onInstalled.addListener((details) => { ); if (details.reason === 'update') { + refreshData(); storage.remove('updateAvailable'); } }); @@ -453,27 +426,24 @@ browser.webRequest.onBeforeRequest.addListener( const { tabId, type, url } = details; if (tabId > -1 && type === 'main_frame') { - const manifest = browser.runtime.getManifest(); - const excludeMatches = manifest.content_scripts[0].exclude_matches; - const excludePatterns = excludeMatches.map(matchToPattern); + const { exclusions, rules } = await getData(); - if (excludePatterns.some((pattern) => new RegExp(pattern).test(url))) { + if (exclusions.domains.some((x) => location.hostname.match(x.replaceAll(/\*/g, '[^ ]*')))) { return; } - const data = await getData(); const hostname = getHostname(url); const state = await getState(hostname); - if (data?.rules?.length) { - const rules = data.rules.map((rule) => ({ + if (rules?.length) { + const rulesWithTabId = rules.map((rule) => ({ ...rule, condition: { ...rule.condition, tabIds: [tabId] }, })); await browser.declarativeNetRequest.updateSessionRules({ - addRules: state.on ? rules : undefined, - removeRuleIds: data.rules.map((rule) => rule.id), + addRules: state.on ? rulesWithTabId : undefined, + removeRuleIds: rules.map((rule) => rule.id), }); } } @@ -486,15 +456,10 @@ browser.webRequest.onBeforeRequest.addListener( */ browser.webRequest.onErrorOccurred.addListener( async (details) => { - const { error, tabId, url } = details; + const { error, tabId } = details; if (error === 'net::ERR_BLOCKED_BY_CLIENT' && tabId > -1) { - const hostname = getHostname(url); - const state = await getState(hostname); - - if (state.on) { - await browser.tabs.sendMessage(tabId, { type: 'INCREASE_ACTIONS_COUNT' }); - } + await browser.tabs.sendMessage(tabId, { type: 'INCREASE_ACTIONS_COUNT', value: error }); } }, { urls: [''] } diff --git a/packages/browser-extension/src/scripts/content.js b/packages/browser-extension/src/scripts/content.js index a2af197..60960c5 100644 --- a/packages/browser-extension/src/scripts/content.js +++ b/packages/browser-extension/src/scripts/content.js @@ -1,9 +1,9 @@ /** - * @typedef {Object} ExtensionData - * @property {string[]} commonWords - * @property {Fix[]} fixes - * @property {{ domains: string[], tags: string[] }} skips - * @property {{ backdrops: string[], classes: string[], containers: string[], selectors: string[] }} tokens + * @typedef {Object} Action + * @property {string} domain + * @property {string} name + * @property {string} [property] + * @property {string} selector */ /** @@ -12,11 +12,24 @@ */ /** - * @typedef {Object} Fix - * @property {string} action - * @property {string} domain - * @property {string} [property] - * @property {string} selector + * @typedef {Object} ExclusionMap + * @property {string[]} domains + * @property {string[]} overflows + * @property {string[]} tags + */ + +/** + * @typedef {Object} ExtensionData + * @property {Action[]} actions + * @property {ExclusionMap} exclusions + * @property {string[]} keywords + * @property {TokenMap} tokens + */ + +/** + * @typedef {Object} GetElementsParams + * @property {boolean} [filterEarly] + * @property {HTMLElement} [from] */ /** @@ -27,9 +40,11 @@ */ /** - * @typedef {Object} GetElementsParams - * @property {boolean} [filterEarly] - * @property {HTMLElement} [from] + * @typedef {Object} TokenMap + * @property {string[]} backdrops + * @property {string[]} classes + * @property {string[]} containers + * @property {string[]} selectors */ /** @@ -42,22 +57,31 @@ if (typeof browser === 'undefined') { } /** - * @description Actions done by the extension - * @type {Set} + * @description Class for request batching */ -const actions = new Set(); +class NotifiableSet extends Set { + constructor(...args) { + super(...args); + } + + add(value) { + super.add(value); + browser.runtime.sendMessage({ type: 'UPDATE_BADGE', value: super.size }); + } +} /** * @description Data object with all the necessary information * @type {ExtensionData} */ -let { commonWords, fixes, skips, tokens } = { - commonWords: [], - fixes: [], - skips: { +let { actions, exclusions, keywords, tokens } = { + actions: [], + exclusions: { domains: [], + overflows: [], tags: [], }, + keywords: [], tokens: { backdrops: [], classes: [], @@ -80,7 +104,13 @@ const hostname = getHostname(); * @description Initial visibility state * @type {boolean} */ -let initiallyVisible = document.visibilityState === 'visible'; +let initiallyVisible = false; + +/** + * @description Log of those steps done by the extension + * @type {NotifiableSet} + */ +const log = new NotifiableSet(); /** * @description Options provided to observer @@ -96,9 +126,9 @@ const seen = new Set(); /** * @description Extension state - * @type {ContentState} + * @type {ContentState | undefined} */ -let state = { on: true }; +let state = undefined; /** * @description Clean DOM @@ -120,8 +150,7 @@ function clean(elements, skipMatch) { if (element instanceof HTMLDialogElement) element.close(); hide(element); - actions.add(`${Date.now()}`); - dispatch({ type: 'UPDATE_BADGE', value: actions.size }); + log.add(`${Date.now()}`); } seen.add(element); @@ -136,11 +165,11 @@ function clean(elements, skipMatch) { } /** - * @description Check if element contains a common word + * @description Check if element contains a keyword * @param {HTMLElement} element */ -function containsCommonWord(element) { - return !!commonWords.length && !!element.outerHTML.match(new RegExp(commonWords.join('|'))); +function hasKeyword(element) { + return !!keywords?.length && !!element.outerHTML.match(new RegExp(keywords.join('|'))); } /** @@ -203,6 +232,21 @@ function getHostname() { return hostname.split('.').slice(-3).join('.').replace('www.', ''); } +/** + * @async + * @description Run if the page wasn't visited yet + * @param {Object} message + * @returns {Promise} + */ +function handleRuntimeMessage(message) { + switch (message.type) { + case 'INCREASE_ACTIONS_COUNT': { + log.add(message.value); + break; + } + } +} + /** * @description Check if an element is visible in the viewport * @param {HTMLElement} element @@ -229,7 +273,7 @@ function isInViewport(element) { * @returns {boolean} */ function match(element, skipMatch) { - if (!tokens.selectors.length || !skips.tags.length) { + if (!exclusions.tags.length || !tokens.selectors.length) { return false; } @@ -243,7 +287,7 @@ function match(element, skipMatch) { const tagName = element.tagName.toUpperCase(); - if (skips.tags.includes(tagName)) { + if (exclusions.tags.includes(tagName)) { return false; } @@ -283,7 +327,7 @@ function filterNodeEarly(node, stopRecursion) { return []; } - if (commonWords && containsCommonWord(node) && !stopRecursion) { + if (hasKeyword(node) && !stopRecursion) { return [node, ...[...node.children].flatMap((node) => filterNodeEarly(node, true))]; } @@ -291,46 +335,27 @@ function filterNodeEarly(node, stopRecursion) { } /** - * @description Fix data, middle consent page and scroll issues + * @description Fix specific cases * @returns {void} */ function fix() { - const backdrops = getElements(tokens.backdrops); - const domains = skips.domains.map((x) => (x.split('.').length < 3 ? `*${x}` : x)); + for (const action of actions) { + const { domain, name, property, selector } = action; - for (const backdrop of backdrops) { - if (backdrop.children.length === 0 && !seen.has(backdrop)) { - actions.add(`${Date.now()}`); - seen.add(backdrop); - hide(backdrop); - } - } - - if (domains.every((x) => !hostname.match(x.replaceAll(/\*/g, '[^ ]*')))) { - for (const element of [document.body, document.documentElement]) { - element?.classList.remove(...(tokens.classes ?? [])); - element?.style.setProperty('position', 'initial', 'important'); - element?.style.setProperty('overflow-y', 'initial', 'important'); - } - } - - for (const fix of fixes) { - const { action, domain, property, selector } = fix; - - if (hostname.includes(domain)) { - switch (action) { + if (hostname.match(domain.replaceAll(/\*/g, '[^ ]*'))) { + switch (name) { case 'click': { const element = document.querySelector(selector); - actions.add('click'); element?.click(); + log.add(name); break; } case 'remove': { const element = document.querySelector(selector); - actions.add('remove'); element?.style?.removeProperty(property); + log.add(name); break; } case 'reload': { @@ -340,38 +365,56 @@ function fix() { case 'reset': { const element = document.querySelector(selector); - actions.add('reset'); element?.style?.setProperty(property, 'initial', 'important'); + log.add(name); break; } case 'resetAll': { const elements = getElements(selector); - actions.add('resetAll'); elements.forEach((e) => e?.style?.setProperty(property, 'initial', 'important')); + log.add(name); break; } } } } + const backdrops = getElements(tokens.backdrops); + + for (const backdrop of backdrops) { + if (backdrop.children.length === 0 && !seen.has(backdrop)) { + log.add(`${Date.now()}`); + seen.add(backdrop); + hide(backdrop); + } + } + + const skips = exclusions.overflows.map((x) => (x.split('.').length < 3 ? `*${x}` : x)); + + if (!skips.some((x) => hostname.match(x.replaceAll(/\*/g, '[^ ]*')))) { + for (const element of [document.body, document.documentElement]) { + element?.classList.remove(...(tokens.classes ?? [])); + element?.style.setProperty('position', 'initial', 'important'); + element?.style.setProperty('overflow-y', 'initial', 'important'); + } + } + const ionRouterOutlet = document.getElementsByTagName('ion-router-outlet')[0]; if (ionRouterOutlet) { - actions.add('ion-router-outlet'); // 2024-08-02: fix #644 temporarily ionRouterOutlet.removeAttribute('inert'); + log.add('ion-router-outlet'); } const t4Wrapper = document.getElementsByClassName('t4-wrapper')[0]; if (t4Wrapper) { - actions.add('t4-wrapper'); + log.add('t4-wrapper'); // 2024-09-12: fix #945 temporarily t4Wrapper.removeAttribute('inert'); } - - dispatch({ type: 'UPDATE_BADGE', value: actions.size }); } /** @@ -412,41 +455,48 @@ function run(params = {}) { * @async * @description Set up the extension * @param {SetUpParams} [params] + * @returns {Promise} */ async function setUp(params = {}) { + const data = await dispatch({ hostname, type: 'GET_DATA' }); + + exclusions = data?.exclusions ?? exclusions; + + if (exclusions.domains.some((x) => location.hostname.match(x.replaceAll(/\*/g, '[^ ]*')))) { + dispatch({ type: 'DISABLE_ICON' }); + observer.disconnect(); + return; + } + state = await dispatch({ hostname, type: 'GET_STATE' }); dispatch({ type: 'ENABLE_POPUP' }); + dispatch({ type: 'ENABLE_REPORT' }); if (state.on) { - const data = await dispatch({ hostname, type: 'GET_DATA' }); + browser.runtime.onMessage.addListener(handleRuntimeMessage); + dispatch({ hostname, type: 'ENABLE_ICON' }); - commonWords = data?.commonWords ?? commonWords; - fixes = data?.fixes ?? fixes; - skips = data?.skips ?? skips; + actions = data?.actions ?? actions; + keywords = data?.keywords ?? keywords; tokens = data?.tokens ?? tokens; - dispatch({ type: 'ENABLE_REPORT' }); - dispatch({ hostname, type: 'ENABLE_ICON' }); - dispatch({ type: 'UPDATE_BADGE', value: actions.size }); observer.observe(document.body ?? document.documentElement, options); if (!params.skipRunFn) run({ containers: tokens.containers }); - } else { - dispatch({ type: 'DISABLE_REPORT' }); - dispatch({ type: 'DISABLE_ICON' }); - dispatch({ type: 'UPDATE_BADGE', value: actions.size }); - observer.disconnect(); } } /** * @description Wait for the body to exist - * @param {void} callback - * @returns {void} + * @returns {Promise} */ async function setUpAfterWaitForBody() { - if (document.body) { - await setUp(); - } else { + if (document.visibilityState === 'visible' && !initiallyVisible) { + if (document.body) { + initiallyVisible = true; + await setUp(); + return; + } + setTimeout(setUpAfterWaitForBody, 50); } } @@ -466,50 +516,6 @@ const observer = new MutationObserver((mutations) => { run({ elements }); }); -/** - * @description Listen to messages from any other scripts - * @listens browser.runtime#onMessage - */ -browser.runtime.onMessage.addListener(async (message) => { - switch (message.type) { - case 'INCREASE_ACTIONS_COUNT': { - actions.add(`${Date.now()}`); - break; - } - } -}); - -/** - * @description Fix bfcache issues - * @listens window#pageshow - * @returns {void} - */ -window.addEventListener('pageshow', async (event) => { - if (document.visibilityState === 'visible' && event.persisted) { - await setUp(); - } -}); - -/** - * @async - * @description Run if the page wasn't visited yet - * @listens window#visibilitychange - * @returns {void} - */ -window.addEventListener('visibilitychange', async () => { - if (document.visibilityState === 'visible') { - if (!initiallyVisible) { - initiallyVisible = true; - await setUp(); - } - - dispatch({ type: state.on ? 'ENABLE_REPORT' : 'DISABLE_REPORT' }); - } -}); - -/** - * @description Run as soon as possible, if the user is in front of the page - */ -if (document.visibilityState === 'visible') { - setUpAfterWaitForBody(); -} +document.addEventListener('visibilitychange', setUpAfterWaitForBody); +window.addEventListener('pageshow', setUpAfterWaitForBody); +setUpAfterWaitForBody();