feat(browser-extension): improve performance when triggering events on DOMContentLoad instead of load, visibilitychange just the first time and minor code improvements

This commit is contained in:
wanhose 2024-08-02 19:24:15 +02:00
parent 7743a37de3
commit 1ea189525b

View File

@ -1,20 +1,15 @@
/**
* @typedef {Object} ExtensionData
* @property {string[] | undefined} commonWords
* @property {string[] | undefined} fixes
* @property {{ domains: string[] | undefined, tags: string[] | undefined } | undefined} skips
* @property {{ classes: string[] | undefined, selectors: string[] | undefined } | undefined} tokens
*/
if (typeof browser === 'undefined') {
browser = chrome;
}
/**
* @typedef {Object} ExtensionData
* @property {string[]} commonWords
* @property {string[]} fixes
* @property {{ domains: string[], tags: string[] }} skips
* @property {{ classes: string[], selectors: string[] }} tokens
*/
/**
* @description Attribute name
*/
const dataAttributeName = 'data-cookie-dialog-monster';
/**
* @description Matched elements count
* @type {number}
@ -27,16 +22,16 @@ let count = 0;
*/
let { commonWords, fixes = [], skips, tokens } = {};
/**
* @description Attribute name
*/
const dataAttributeName = 'data-cookie-dialog-monster';
/**
* @description Shortcut to send messages to background script
*/
const dispatch = browser.runtime.sendMessage;
/**
* @description Event name
*/
const setupEventName = 'cookie-dialog-monster';
/**
* @description Current hostname
* @type {string}
@ -44,10 +39,10 @@ const setupEventName = 'cookie-dialog-monster';
const hostname = getHostname();
/**
* @description Elements that were already matched and are removable
* @type {HTMLElement[]}
* @description Initial visibility state
* @type {boolean}
*/
const removables = [];
let initiallyVisible = document.visibilityState === 'visible';
/**
* @description Options provided to observer
@ -60,6 +55,12 @@ const options = { childList: true, subtree: true };
*/
const preview = hostname.startsWith('consent.') || hostname.startsWith('myprivacy.');
/**
* @description Elements that were already matched and are removable
* @type {HTMLElement[]}
*/
const removables = [];
/**
* @description Elements that were already seen
* @type {HTMLElement[]}
@ -72,6 +73,11 @@ const seen = [];
*/
let state = { enabled: true };
/**
* @description Event name to trigger the cleaning process
*/
const triggerEventName = 'cookie-dialog-monster';
/**
* @description Clean DOM
* @param {Element[]} elements
@ -108,8 +114,10 @@ function clean(elements, skipMatch) {
function forceClean(element) {
const elements = [...element.querySelectorAll(tokens.selectors)];
fix();
if (elements.length && !preview) clean(elements, true);
if (elements.length) {
fix();
clean(elements, true);
}
}
/**
@ -169,7 +177,12 @@ function isInViewport(element) {
* @returns {boolean}
*/
function match(element, skipMatch) {
if (!tokens?.classes.length || !tokens?.selectors.length) {
if (
!commonWords.length ||
!tokens?.classes?.length ||
!tokens?.selectors?.length ||
!skips?.tags?.length
) {
return false;
}
@ -215,7 +228,7 @@ function match(element, skipMatch) {
}
/**
* @description Fix data, consent page and scroll issues
* @description Fix data, middle consent page and scroll issues
* @returns {void}
*/
function fix() {
@ -310,9 +323,8 @@ function restoreDOM() {
/**
* @async
* @description Set up everything
* @param {boolean} skipReadyStateHack
*/
async function setup(skipReadyStateHack) {
async function setup() {
state = (await dispatch({ hostname, type: 'GET_HOSTNAME_STATE' })) ?? state;
dispatch({ type: 'ENABLE_POPUP' });
@ -328,11 +340,6 @@ async function setup(skipReadyStateHack) {
dispatch({ type: 'SET_BADGE', value: `${count}` });
}
// 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(setupEventName));
}
dispatch({ type: 'ENABLE_ICON' });
observer.observe(document.body ?? document.documentElement, options);
} else {
@ -352,15 +359,14 @@ const observer = new MutationObserver((mutations) => {
const elements = mutations.flatMap((mutation) => Array.from(mutation.addedNodes));
fix();
clean(elements);
window.dispatchEvent(new Event(triggerEventName, { detail: { elements } }));
});
/**
* @description Listen to messages from any other scripts
* @listens browser.runtime#onMessage
*/
browser.runtime.onMessage.addListener((message) => {
browser.runtime.onMessage.addListener(async (message) => {
switch (message.type) {
case 'RESTORE': {
restoreDOM();
@ -372,7 +378,55 @@ browser.runtime.onMessage.addListener((message) => {
}
}
setup();
fix();
await setup();
});
/**
* @async
* @description Fix still existing elements when page loads
* @listens window#DOMContentLoaded
* @returns {void}
*/
window.addEventListener('DOMContentLoaded', async () => {
if (document.visibilityState === 'visible') {
await setup();
window.dispatchEvent(new Event(triggerEventName));
}
});
/**
* @description Fix bfcache issues
* @listens window#pageshow
* @returns {void}
*/
window.addEventListener('pageshow', async (event) => {
if (document.visibilityState === 'visible' && event.persisted) {
await setup();
window.dispatchEvent(new Event(triggerEventName));
}
});
/**
* @description Force clean when this event is fired
* @listens window#cookie-dialog-monster
* @returns {void}
*/
window.addEventListener(triggerEventName, (event) => {
if (document.body?.children.length && !preview && state.enabled && tokens?.selectors?.length) {
fix();
if (event.detail?.elements) {
clean(event.detail.elements);
} else {
if (readingTime() < 4) {
forceClean(document.body);
} else {
// 2023-06-13: look into the first level of the document body, there are dialogs there very often
clean([...document.body.children]);
}
}
}
});
/**
@ -382,51 +436,9 @@ browser.runtime.onMessage.addListener((message) => {
* @returns {void}
*/
window.addEventListener('visibilitychange', async () => {
if (document.body?.children.length && !tokens) {
await setup(true);
clean([...document.body.children]);
if (document.visibilityState === 'visible' && !initiallyVisible) {
initiallyVisible = true;
await setup();
window.dispatchEvent(new Event(triggerEventName));
}
});
/**
* @description Fix still existing elements when page fully load
* @listens window#load
* @returns {void}
*/
window.addEventListener('load', () => {
if (document.visibilityState === 'visible') {
window.dispatchEvent(new Event(setupEventName));
}
});
/**
* @description Fix bfcache issues
* @listens window#pageshow
* @returns {void}
*/
window.addEventListener('pageshow', (event) => {
if (document.visibilityState === 'visible' && event.persisted) {
setup(true);
window.dispatchEvent(new Event(setupEventName));
}
});
/**
* @description Force clean when this event is fired
* @listens window#cookie-dialog-monster
* @returns {void}
*/
window.addEventListener(setupEventName, () => {
if (document.body?.children.length && state.enabled && tokens?.selectors.length && !preview) {
if (readingTime() < 4) {
forceClean(document.body);
} else {
// 2023-06-13: look into the first level of the document body, there are dialogs there very often
clean([...document.body.children]);
}
}
});
if (document.visibilityState === 'visible') {
setup();
}