Merge pull request '8.0.2' (#155) from v8.0.2 into main

Reviewed-on: #155
This commit is contained in:
wanhose 2024-10-19 07:44:05 +00:00
commit a38d77d46d
3 changed files with 145 additions and 196 deletions

View File

@ -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": {
@ -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"

View File

@ -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: ['<all_urls>'] }

View File

@ -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<string>}
* @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<string>}
*/
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<void>}
*/
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<void>}
*/
async function setUp(params = {}) {
state = await dispatch({ hostname, type: 'GET_STATE' });
dispatch({ type: 'ENABLE_POPUP' });
if (state.on) {
const data = await dispatch({ hostname, type: 'GET_DATA' });
commonWords = data?.commonWords ?? commonWords;
fixes = data?.fixes ?? fixes;
skips = data?.skips ?? skips;
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) {
browser.runtime.onMessage.addListener(handleRuntimeMessage);
dispatch({ hostname, type: 'ENABLE_ICON' });
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<void>}
*/
async function setUpAfterWaitForBody() {
if (document.visibilityState === 'visible' && !initiallyVisible) {
if (document.body) {
initiallyVisible = true;
await setUp();
} else {
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();