/** * @description Number of attempts * @type {number} */ let attempts = 1; /** * @description Array of selectors * @type {string[]} */ let classesFromNetwork = []; /** * @description Shortcut to send messages to background script * @type {void} */ const dispatch = chrome.runtime.sendMessage; /** * @description Array of selectors * @type {string[]} */ let selectorsFromCache = []; /** * @description Array of selectors * @type {Promise[]} */ let selectorsFromNetwork = []; /** * @description Split large arrays into promises * @param {string[]} array */ const chunkerize = (array) => [...Array(Math.ceil(array.length / 300))].map( (_, index) => () => new Promise((resolve) => { removeElements(array.slice(index * 300, (index + 1) * 300), true); resolve(true); }) ); /** * @description Fixes scroll issues */ const fix = () => { const body = document.body; const classes = classesFromNetwork; const facebook = document.getElementsByClassName("_31e")[0]; const html = document.documentElement; if (body && classes.length > 0) body.classList.remove(...classes); if (body) body.style.setProperty("overflow-y", "unset", "important"); if (facebook) facebook.style.setProperty("position", "unset", "important"); if (html) html.style.setProperty("overflow-y", "unset", "important"); }; /** * @function removeElements * @description Removes matched elements from a selectors array * @param {string[]} selectors * @param {boolean} updateCache */ const removeElements = (selectors, updateCache) => { for (let i = selectors.length; i--; ) { const selector = selectors[i]; const element = search(selector); if (element) { const tagName = element.tagName.toUpperCase(); if (!["BODY", "HTML"].includes(tagName)) { element.remove(); if (updateCache) { selectorsFromCache = [...selectorsFromCache, selector]; dispatch({ hostname: document.location.hostname, state: { matches: [selector] }, type: "UPDATE_CACHE", }); } } } } }; /** * @function runTasks * @description Starts running tasks */ const runTasks = async () => { if (attempts <= 20) { fix(); removeElements(selectorsFromCache); if (selectorsFromNetwork.length > 0) { const selectors = selectorsFromNetwork; if (attempts <= 5) await Promise.all(selectors.map((fn) => fn())); if (document.readyState === "complete") attempts += 1; } } }; /** * @function search * @description Retrieves HTML element if selector exists * * @param {string} selector * @returns {HTMLElement | null} An HTML element or null */ const search = (selector) => { if (!selector.includes("[") && !selector.includes(">")) { if (selector.startsWith(".")) { return document.getElementsByClassName(selector.slice(1))[0]; } if (selector.startsWith("#")) { return document.getElementById(selector.slice(1)); } } else { return document.querySelector(selector); } return null; }; /** * @description Setups classes selectors * @type {Promise} */ const setupClasses = new Promise((resolve) => { dispatch({ type: "GET_CLASSES" }, null, ({ classes }) => { classesFromNetwork = classes; resolve(true); }); }); /** * @description Setups elements selectors * @type {Promise} */ const setupSelectors = new Promise((resolve) => { dispatch({ type: "GET_SELECTORS" }, null, ({ selectors }) => { selectorsFromNetwork = chunkerize(selectors); resolve(true); }); }); dispatch( { hostname: document.location.hostname, type: "GET_CACHE" }, null, async ({ enabled, matches }) => { dispatch({ type: "ENABLE_POPUP" }); if (enabled) { selectorsFromCache = matches; dispatch({ type: "ENABLE_ICON" }); await Promise.all([setupClasses, setupSelectors]); await runTasks(); setInterval(runTasks, 500); } } );