diff --git a/packages/browser-extension/src/scripts/background.js b/packages/browser-extension/src/scripts/background.js index c1f1057..169204e 100644 --- a/packages/browser-extension/src/scripts/background.js +++ b/packages/browser-extension/src/scripts/background.js @@ -38,11 +38,26 @@ const script = browser.scripting; */ const storage = browser.storage.local; +/** + * @description Supress `browser.runtime.lastError` + */ +const suppressLastError = () => void browser.runtime.lastError; + +/** + * @description Map match to pattern format + * @param {string} match + * @returns {string} + */ +function matchToPattern(match) { + return `^${match.replaceAll('*.', '*(.)?').replaceAll('*', '.*')}$`; +} + /** * @description Refresh data * @param {void?} callback + * @returns {void} */ -const refreshData = (callback) => { +function refreshData(callback) { try { fetch(`${apiUrl}/data/`).then((result) => { result.json().then(({ data }) => { @@ -53,7 +68,7 @@ const refreshData = (callback) => { } catch { refreshData(callback); } -}; +} /** * @async @@ -61,8 +76,9 @@ const refreshData = (callback) => { * @param {any} message * @param {browser.tabs.Tab} tab * @param {void?} callback + * @returns {void} */ -const report = async (message, tab, callback) => { +async function report(message, tab, callback) { try { const reason = message.reason; const url = message.url; @@ -76,12 +92,7 @@ const report = async (message, tab, callback) => { } catch { console.error("Can't send report"); } -}; - -/** - * @description Supress `browser.runtime.lastError` - */ -const suppressLastError = () => void browser.runtime.lastError; +} /** * @description Listen to context menus clicked @@ -159,19 +170,9 @@ browser.runtime.onMessage.addListener((message, sender, callback) => { script.insertCSS({ files: ['styles/dialog.css'], target: { tabId } }); } break; - case 'INSERT_EXTENSION_CSS': - if (isPage && tabId !== undefined) { - script.insertCSS({ files: ['styles/extension.css'], target: { tabId } }); - } - break; case 'REFRESH_DATA': refreshData(callback); return true; - case 'RELOAD_TAB': - if (tabId !== undefined) { - browser.tabs.reload(tabId, { bypassCache: true }); - } - break; case 'REPORT': if (tabId !== undefined) { report(message, sender.tab, callback); @@ -199,10 +200,12 @@ browser.runtime.onMessage.addListener((message, sender, callback) => { * @description Listens to extension installed */ browser.runtime.onInstalled.addListener(() => { + const documentUrlPatterns = browser.runtime.getManifest().content_scripts[0].matches; + browser.contextMenus.create( { contexts: ['all'], - documentUrlPatterns: browser.runtime.getManifest().content_scripts[0].matches, + documentUrlPatterns, id: extensionMenuItemId, title: 'Cookie Dialog Monster', }, @@ -211,7 +214,7 @@ browser.runtime.onInstalled.addListener(() => { browser.contextMenus.create( { contexts: ['all'], - documentUrlPatterns: browser.runtime.getManifest().content_scripts[0].matches, + documentUrlPatterns, id: settingsMenuItemId, parentId: extensionMenuItemId, title: browser.i18n.getMessage('contextMenu_settingsOption'), @@ -221,7 +224,7 @@ browser.runtime.onInstalled.addListener(() => { browser.contextMenus.create( { contexts: ['all'], - documentUrlPatterns: browser.runtime.getManifest().content_scripts[0].matches, + documentUrlPatterns, id: reportMenuItemId, parentId: extensionMenuItemId, title: browser.i18n.getMessage('contextMenu_reportOption'), @@ -239,6 +242,7 @@ browser.runtime.onStartup.addListener(() => { /** * @description Listen to the moment before a request is made to apply the rules + * @returns {Promise} */ browser.webRequest.onBeforeRequest.addListener( async (details) => { @@ -247,9 +251,7 @@ browser.webRequest.onBeforeRequest.addListener( if (tabId > -1 && type === 'main_frame') { const manifest = browser.runtime.getManifest(); const excludeMatches = manifest.content_scripts[0].exclude_matches; - const excludePatterns = excludeMatches.map( - (match) => `^${match.replaceAll('*.', '*(.)?').replaceAll('*', '.*')}$` - ); + const excludePatterns = excludeMatches.map(matchToPattern); if (excludePatterns.some((pattern) => new RegExp(pattern).test(url))) { return; @@ -260,13 +262,13 @@ browser.webRequest.onBeforeRequest.addListener( const state = store[hostname] ?? { enabled: true }; if (data?.rules?.length) { - browser.declarativeNetRequest.updateSessionRules({ - addRules: state.enabled - ? data.rules.map((rule) => ({ - ...rule, - condition: { ...rule.condition, tabIds: [tabId] }, - })) - : undefined, + const rules = data.rules.map((rule) => ({ + ...rule, + condition: { ...rule.condition, tabIds: [tabId] }, + })); + + await browser.declarativeNetRequest.updateSessionRules({ + addRules: state.enabled ? rules : undefined, removeRuleIds: data.rules.map((rule) => rule.id), }); } @@ -274,3 +276,27 @@ browser.webRequest.onBeforeRequest.addListener( }, { urls: [''] } ); + +/** + * @description Listen for errors on network requests + */ +browser.webRequest.onErrorOccurred.addListener( + async (details) => { + const { error, tabId, url } = details; + + if (tabId > -1) { + const hostname = new URL(url).hostname.split('.').slice(-3).join('.').replace('www.', ''); + const { data, ...store } = await storage.get(['data', hostname]); + const state = store[hostname] ?? { enabled: true }; + + if (error === 'net::ERR_BLOCKED_BY_CLIENT' && state.enabled) { + const sessionRules = await browser.declarativeNetRequest.getSessionRules(); + + if (sessionRules.some((rule) => new RegExp(rule.condition.urlFilter).test(url))) { + await browser.tabs.sendMessage(tabId, { type: 'INCREASE_ACTIONS_COUNT' }); + } + } + } + }, + { urls: [''] } +); diff --git a/packages/browser-extension/src/scripts/content.js b/packages/browser-extension/src/scripts/content.js index 5f24ed1..062440c 100644 --- a/packages/browser-extension/src/scripts/content.js +++ b/packages/browser-extension/src/scripts/content.js @@ -92,13 +92,7 @@ const options = { childList: true, subtree: true }; * @description Elements that were already matched and are removable * @type {Set} */ -const removables = new Set(); - -/** - * @description Elements that were already seen - * @type {HTMLElement[]} - */ -const seen = []; +const seen = new Set(); /** * @description Extension state @@ -124,14 +118,13 @@ function clean(elements, skipMatch) { if (match(element, skipMatch)) { if (element instanceof HTMLDialogElement) element.close(); - else element.setAttribute(dataAttributeName, 'true'); + hide(element); actions.add(new Date().getTime().toString()); dispatch({ type: 'SET_BADGE', value: actions.size }); - removables.add(element); } - seen.push(element); + seen.add(element); } if (index < elements.length) { @@ -248,7 +241,7 @@ function match(element, skipMatch) { return false; } - if (seen.includes(element)) { + if (seen.has(element)) { return false; } @@ -384,6 +377,19 @@ function fix() { dispatch({ type: 'SET_BADGE', value: actions.size }); } +/** + * @description Hide DOM element + * @param {HTMLElement} element + * @returns {void} + */ +function hide(element) { + element.style.setProperty('clip-path', 'circle(0px)', 'important'); + element.style.setProperty('display', 'none', 'important'); + element.style.setProperty('height', '0px', 'important'); + element.style.setProperty('overflow', 'hidden', 'important'); + element.style.setProperty('transform', 'scale(0)', 'important'); +} + /** * @description Clean DOM when this function is called * @param {RunParams} [params] @@ -423,7 +429,6 @@ async function setUp(params = {}) { tokens = data?.tokens ?? tokens; dispatch({ type: 'ENABLE_ICON' }); - dispatch({ type: 'INSERT_EXTENSION_CSS' }); dispatch({ type: 'SET_BADGE', value: actions.size }); observer.observe(document.body ?? document.documentElement, options); if (!params.skipRunFn) run({ containers: tokens.containers }); @@ -455,13 +460,8 @@ const observer = new MutationObserver((mutations) => { */ browser.runtime.onMessage.addListener(async (message) => { switch (message.type) { - case 'RESTORE': { - await dispatch({ type: 'RELOAD_TAB' }); - break; - } - case 'RUN': { - await setUp({ skipRunFn: !!removables.size }); - run({ elements: [...removables], skipMatch: true }); + case 'INCREASE_ACTIONS_COUNT': { + actions.add(new Date().getTime().toString()); break; } } diff --git a/packages/browser-extension/src/scripts/popup.js b/packages/browser-extension/src/scripts/popup.js index 2d9afc4..27ad6a6 100644 --- a/packages/browser-extension/src/scripts/popup.js +++ b/packages/browser-extension/src/scripts/popup.js @@ -146,28 +146,24 @@ async function handleLinkRedirect(event) { } /** - * @async * @description Disable or enable extension on current page - * @param {MouseEvent} event - * @returns {Promise} + * @returns {void} */ -async function handlePowerToggle(event) { - const target = event.currentTarget; +function handlePowerToggle() { const next = { enabled: !state.enabled }; browser.runtime.sendMessage({ hostname, state: next, type: 'SET_HOSTNAME_STATE' }); - browser.tabs.sendMessage(state.tabId, { type: next.enabled ? 'RUN' : 'RESTORE' }); - target.setAttribute('aria-disabled', 'true'); - target.setAttribute('data-value', next.enabled ? 'on' : 'off'); + browser.tabs.reload(state.tabId, { bypassCache: true }); window.close(); } /** + * @async * @description Open options page - * @returns {void} + * @returns {Promise} */ -function handleSettingsClick() { - browser.runtime.openOptionsPage(); +async function handleSettingsClick() { + await browser.runtime.openOptionsPage(); } /** diff --git a/packages/browser-extension/src/styles/extension.css b/packages/browser-extension/src/styles/extension.css deleted file mode 100644 index e67d723..0000000 --- a/packages/browser-extension/src/styles/extension.css +++ /dev/null @@ -1,8 +0,0 @@ -*[data-cookie-dialog-monster='true'] { - clip-path: circle(0px) !important; - display: none !important; - height: 0px !important; - overflow: hidden !important; - transform: scale(0) !important; - visibility: hidden !important; -}